Loading pageā¦
Rust walkthroughs
Loading pageā¦
serde::ser::Impossible for implementing dummy serializers?serde::ser::Impossible is a helper type that implements all Serializer traits with methods that always panic, allowing you to implement only the specific serialization methods you need without manually stubbing out dozens of unused trait methods. When implementing serde::Serializer for a custom type, the trait requires implementing many methods like serialize_bool, serialize_i32, serialize_seq, serialize_map, and moreābut if your serializer only handles a subset of types, you'd need to write unimplemented!() for each unused method. Impossible<T> provides this stub implementation automatically, panicking at runtime if any unimplemented method is called, making it ideal for serializers that accept only specific types like integers or strings, serializers that produce a subset of output formats, and test stubs that verify which serialization methods get invoked.
use serde::Serializer;
// The Serializer trait requires implementing many methods
// Impossible provides default implementations that panic
// Without Impossible, you'd need to implement all of these:
// - serialize_bool
// - serialize_i8, serialize_i16, serialize_i32, serialize_i64, serialize_i128
// - serialize_u8, serialize_u16, serialize_u32, serialize_u64, serialize_u128
// - serialize_f32, serialize_f64
// - serialize_char
// - serialize_str
// - serialize_bytes
// - serialize_none, serialize_some
// - serialize_unit, serialize_unit_struct, serialize_unit_variant
// - serialize_newtype_struct, serialize_newtype_variant
// - serialize_seq, serialize_tuple, serialize_tuple_struct, serialize_tuple_variant
// - serialize_map, serialize_struct, serialize_struct_variant
fn main() {
println!("Serializer trait has ~30+ methods to implement");
}The full Serializer trait has many required methods; Impossible stubs them all.
use serde::ser::{self, Serializer, Impossible};
use std::marker::PhantomData;
// A serializer that only outputs strings
struct StringOnlySerializer {
output: String,
}
impl Serializer for StringOnlySerializer {
type Ok = String;
type Error = ser::Error;
// Only implement what we need - in this case, strings
fn serialize_str(mut self, v: &str) -> Result<Self::Ok, Self::Error> {
Ok(v.to_string())
}
// Impossible handles all other methods by panicking
// We inherit all other serialize_* methods from Impossible
type SerializeSeq = Impossible<String, ser::Error>;
type SerializeTuple = Impossible<String, ser::Error>;
type SerializeTupleStruct = Impossible<String, ser::Error>;
type SerializeTupleVariant = Impossible<String, ser::Error>;
type SerializeMap = Impossible<String, ser::Error>;
type SerializeStruct = Impossible<String, ser::Error>;
type SerializeStructVariant = Impossible<String, ser::Error>;
}
fn main() {
let serializer = StringOnlySerializer { output: String::new() };
// This works - we implemented serialize_str
let result = "hello".serialize(serializer);
println!("Result: {:?}", result); // Ok("hello")
}Impossible stubs out all methods we don't implement, panicking if called.
use serde::{ser, Serializer, Serialize};
// Without Impossible, we'd need to implement every method
struct StringOnlyManual {
output: String,
}
impl Serializer for StringOnlyManual {
type Ok = String;
type Error = ser::Error;
fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
Ok(v.to_string())
}
// All these would need manual stub implementations:
fn serialize_bool(self, _: bool) -> Result<Self::Ok, Self::Error> {
unimplemented!("bool not supported")
}
fn serialize_i32(self, _: i32) -> Result<Self::Ok, Self::Error> {
unimplemented!("i32 not supported")
}
fn serialize_u32(self, _: u32) -> Result<Self::Ok, Self::Error> {
unimplemented!("u32 not supported")
}
// ... 30+ more methods ...
// This gets tedious fast!
type SerializeSeq = Impossible<String, ser::Error>;
type SerializeTuple = Impossible<String, ser::Error>;
type SerializeTupleStruct = Impossible<String, ser::Error>;
type SerializeTupleVariant = Impossible<String, ser::Error>;
type SerializeMap = Impossible<String, ser::Error>;
type SerializeStruct = Impossible<String, ser::Error>;
type SerializeStructVariant = Impossible<String, ser::Error>;
}
fn main() {
println!("Manual implementation requires stubbing ~30 methods");
}Manual implementation requires writing stub methods for everything you don't support.
use serde::ser::{self, Serializer, Impossible};
use std::marker::PhantomData;
// A serializer that only handles integers
struct IntegerSerializer;
impl Serializer for IntegerSerializer {
type Ok = i64;
type Error = ser::Error;
// Implement only integer types
fn serialize_i8(self, v: i8) -> Result<Self::Ok, Self::Error> {
Ok(v as i64)
}
fn serialize_i16(self, v: i16) -> Result<Self::Ok, Self::Error> {
Ok(v as i64)
}
fn serialize_i32(self, v: i32) -> Result<Self::Ok, Self::Error> {
Ok(v as i64)
}
fn serialize_i64(self, v: i64) -> Result<Self::Ok, Self::Error> {
Ok(v)
}
fn serialize_u8(self, v: u8) -> Result<Self::Ok, Self::Error> {
Ok(v as i64)
}
fn serialize_u16(self, v: u16) -> Result<Self::Ok, Self::Error> {
Ok(v as i64)
}
fn serialize_u32(self, v: u32) -> Result<Self::Ok, Self::Error> {
Ok(v as i64)
}
// Impossible provides all other methods
type SerializeSeq = Impossible<i64, ser::Error>;
type SerializeTuple = Impossible<i64, ser::Error>;
type SerializeTupleStruct = Impossible<i64, ser::Error>;
type SerializeTupleVariant = Impossible<i64, ser::Error>;
type SerializeMap = Impossible<i64, ser::Error>;
type SerializeStruct = Impossible<i64, ser::Error>;
type SerializeStructVariant = Impossible<i64, ser::Error>;
}
fn main() {
// Works for integers
let result = 42i32.serialize(IntegerSerializer);
println!("Result: {:?}", result); // Ok(42)
// Would panic for strings - not implemented
// let result = "hello".serialize(IntegerSerializer);
// thread 'main' panicked at 'not implemented'
}Implement only what you need; Impossible handles the rest with panics.
use serde::ser::{self, Impossible};
// Impossible<Ok, Error> has two type parameters:
// - Ok: The return type for serializer methods
// - Error: The error type for serialization failures
fn main() {
// Example: Impossible returning String
type StringImpossible = Impossible<String, ser::Error>;
// Example: Impossible returning ()
type UnitImpossible = Impossible<(), ser::Error>;
// Example: Impossible with custom error type
#[derive(Debug)]
struct MyError(String);
impl std::fmt::Display for MyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::error::Error for MyError {}
impl ser::Error for MyError {
fn custom<T: std::fmt::Display>(msg: T) -> Self {
MyError(msg.to_string())
}
}
type CustomImpossible = Impossible<String, MyError>;
println!("Impossible is generic over Ok and Error types");
}Impossible<Ok, Error> matches the serializer's output and error types.
use serde::ser::{self, Serializer, Impossible, SerializeSeq};
// For compound types like sequences, we need additional types
struct CountSerializer;
impl Serializer for CountSerializer {
type Ok = usize; // Return count of items
type Error = ser::Error;
// Count single items as 1
fn serialize_i32(self, _: i32) -> Result<Self::Ok, Self::Error> {
Ok(1)
}
fn serialize_str(self, _: &str) -> Result<Self::Ok, Self::Error> {
Ok(1)
}
// For sequences, we need a separate type
type SerializeSeq = CountSequence;
fn serialize_seq(self, _: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
Ok(CountSequence { count: 0 })
}
// Use Impossible for other compound types we don't support
type SerializeTuple = Impossible<usize, ser::Error>;
type SerializeTupleStruct = Impossible<usize, ser::Error>;
type SerializeTupleVariant = Impossible<usize, ser::Error>;
type SerializeMap = Impossible<usize, ser::Error>;
type SerializeStruct = Impossible<usize, ser::Error>;
type SerializeStructVariant = Impossible<usize, ser::Error>;
}
struct CountSequence {
count: usize,
}
impl SerializeSeq for CountSequence {
type Ok = usize;
type Error = ser::Error;
fn serialize_element<T: ?Sized + ser::Serialize>(&mut self, _: &T) -> Result<(), Self::Error> {
self.count += 1;
Ok(())
}
fn end(self) -> Result<Self::Ok, Self::Error> {
Ok(self.count)
}
}
fn main() {
let serializer = CountSerializer;
// Serialize a sequence and count items
let data = vec
![1, 2, 3, 4, 5];
let count = serde::Serialize::serialize(&data, serializer).unwrap();
println!("Counted {} items", count);
}For compound types, you define separate types; Impossible works for unsupported compound types.
use serde::ser::{self, Serializer, Impossible};
use std::cell::RefCell;
// A test serializer that records which methods were called
struct TestSerializer {
calls: RefCell<Vec<String>>,
}
impl Serializer for TestSerializer {
type Ok = Vec<String>;
type Error = ser::Error;
fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error> {
self.calls.borrow_mut().push(format!("bool({})", v));
Ok(self.calls.borrow().clone())
}
fn serialize_i32(self, v: i32) -> Result<Self::Ok, Self::Error> {
self.calls.borrow_mut().push(format!("i32({})", v));
Ok(self.calls.borrow().clone())
}
fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
self.calls.borrow_mut().push(format!("str(\"{}\")", v));
Ok(self.calls.borrow().clone())
}
// Use Impossible for everything else - will panic if called
type SerializeSeq = Impossible<Vec<String>, ser::Error>;
type SerializeTuple = Impossible<Vec<String>, ser::Error>;
type SerializeTupleStruct = Impossible<Vec<String>, ser::Error>;
type SerializeTupleVariant = Impossible<Vec<String>, ser::Error>;
type SerializeMap = Impossible<Vec<String>, ser::Error>;
type SerializeStruct = Impossible<Vec<String>, ser::Error>;
type SerializeStructVariant = Impossible<Vec<String>, ser::Error>;
}
#[derive(serde::Serialize)]
struct TestData {
value: i32,
name: String,
}
fn main() {
// Test that our serialization calls the right methods
// Note: structs need SerializeStruct, which we didn't implement
// This would panic:
// let result = TestData { value: 42, name: "test".to_string() }
// .serialize(TestSerializer { calls: RefCell::new(vec
![]) });
// Instead, test primitives:
let serializer = TestSerializer { calls: RefCell::new(vec
![]) };
let result = 42i32.serialize(serializer).unwrap();
println!("Calls: {:?}", result); // ["i32(42)"]
let serializer = TestSerializer { calls: RefCell::new(vec![]) };
let result = "hello".serialize(serializer).unwrap();
println!("Calls: {:?}", result); // ["str(\"hello\")"]
}Use Impossible for test doubles to catch unexpected serialization paths.
use serde::ser::{self, Serializer, Impossible};
#[derive(Debug)]
struct SerializationError {
message: String,
}
impl std::fmt::Display for SerializationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Serialization error: {}", self.message)
}
}
impl std::error::Error for SerializationError {}
impl ser::Error for SerializationError {
fn custom<T: std::fmt::Display>(msg: T) -> Self {
SerializationError { message: msg.to_string() }
}
}
struct JsonSubsetSerializer;
impl Serializer for JsonSubsetSerializer {
type Ok = String;
type Error = SerializationError;
fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
Ok(format!("\"{}\"", v))
}
fn serialize_i32(self, v: i32) -> Result<Self::Ok, Self::Error> {
Ok(v.to_string())
}
fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error> {
Ok(v.to_string())
}
// Impossible with custom error type
type SerializeSeq = Impossible<String, SerializationError>;
type SerializeTuple = Impossible<String, SerializationError>;
type SerializeTupleStruct = Impossible<String, SerializationError>;
type SerializeTupleVariant = Impossible<String, SerializationError>;
type SerializeMap = Impossible<String, SerializationError>;
type SerializeStruct = Impossible<String, SerializationError>;
type SerializeStructVariant = Impossible<String, SerializationError>;
}
fn main() {
let result = "hello".serialize(JsonSubsetSerializer);
println!("Result: {:?}", result); // Ok("\"hello\"")
}Custom error types work with Impossible for consistent error handling.
use serde::ser::{self, Serializer, Impossible};
struct OptionSerializer;
impl Serializer for OptionSerializer {
type Ok = bool; // Returns true if Some, false if None
type Error = ser::Error;
fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
Ok(false)
}
fn serialize_some<T: ?Sized + ser::Serialize>(self, _: T) -> Result<Self::Ok, Self::Error> {
Ok(true)
}
// Everything else uses Impossible
type SerializeSeq = Impossible<bool, ser::Error>;
type SerializeTuple = Impossible<bool, ser::Error>;
type SerializeTupleStruct = Impossible<bool, ser::Error>;
type SerializeTupleVariant = Impossible<bool, ser::Error>;
type SerializeMap = Impossible<bool, ser::Error>;
type SerializeStruct = Impossible<bool, ser::Error>;
type SerializeStructVariant = Impossible<bool, ser::Error>;
}
fn main() {
let some_value: Option<i32> = Some(42);
let none_value: Option<i32> = None;
let result_some = some_value.serialize(OptionSerializer).unwrap();
let result_none = none_value.serialize(OptionSerializer).unwrap();
println!("Some: {}", result_some); // true
println!("None: {}", result_none); // false
}Implement serialize_none and serialize_some for Option support.
use serde::ser::{self, Serializer, Impossible};
struct UnitSerializer;
impl Serializer for UnitSerializer {
type Ok = ();
type Error = ser::Error;
// Unit types - useful for side-effect serializers
fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
println!("Unit serialized");
Ok(())
}
fn serialize_unit_struct(self, name: &'static str) -> Result<Self::Ok, Self::Error> {
println!("Unit struct '{}' serialized", name);
Ok(())
}
fn serialize_unit_variant(
self,
name: &'static str,
_: u32,
variant: &'static str,
) -> Result<Self::Ok, Self::Error> {
println!("Unit variant '{}'::{} serialized", name, variant);
Ok(())
}
type SerializeSeq = Impossible<(), ser::Error>;
type SerializeTuple = Impossible<(), ser::Error>;
type SerializeTupleStruct = Impossible<(), ser::Error>;
type SerializeTupleVariant = Impossible<(), ser::Error>;
type SerializeMap = Impossible<(), ser::Error>;
type SerializeStruct = Impossible<(), ser::Error>;
type SerializeStructVariant = Impossible<(), ser::Error>;
}
#[derive(serde::Serialize)]
struct MyUnit;
#[derive(serde::Serialize)]
enum MyEnum {
A,
B,
}
fn main() {
().serialize(UnitSerializer).unwrap();
MyUnit.serialize(UnitSerializer).unwrap();
MyEnum::A.serialize(UnitSerializer).unwrap();
}Unit serialization is useful for validation or side-effect serializers.
use serde::ser::{self, Serializer, Impossible};
use serde::Serialize;
struct OnlyBool;
impl Serializer for OnlyBool {
type Ok = bool;
type Error = ser::Error;
fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error> {
Ok(v)
}
type SerializeSeq = Impossible<bool, ser::Error>;
type SerializeTuple = Impossible<bool, ser::Error>;
type SerializeTupleStruct = Impossible<bool, ser::Error>;
type SerializeTupleVariant = Impossible<bool, ser::Error>;
type SerializeMap = Impossible<bool, ser::Error>;
type SerializeStruct = Impossible<bool, ser::Error>;
type SerializeStructVariant = Impossible<bool, ser::Error>;
}
fn main() {
// This works:
let result = true.serialize(OnlyBool).unwrap();
println!("Bool: {}", result);
// This would panic:
// let result = 42i32.serialize(OnlyBool);
// thread 'main' panicked at 'not implemented: serialize_i32'
// The panic message tells you which method was called
println!("Impossible panics with method name");
}Impossible panic messages identify the unimplemented method.
use serde::ser::{self, Serializer, Impossible, SerializeStruct};
// A serializer that only handles structs with specific field names
struct FieldNameSerializer {
fields: Vec<String>,
}
impl Serializer for FieldNameSerializer {
type Ok = Vec<String>;
type Error = ser::Error;
type SerializeSeq = Impossible<Vec<String>, ser::Error>;
type SerializeTuple = Impossible<Vec<String>, ser::Error>;
type SerializeTupleStruct = Impossible<Vec<String>, ser::Error>;
type SerializeTupleVariant = Impossible<Vec<String>, ser::Error>;
type SerializeMap = Impossible<Vec<String>, ser::Error>;
// We implement struct serialization
type SerializeStruct = FieldNameStruct;
fn serialize_struct(
self,
_: &'static str,
_len: usize,
) -> Result<Self::SerializeStruct, Self::Error> {
Ok(FieldNameStruct { fields: Vec::new() })
}
type SerializeStructVariant = Impossible<Vec<String>, ser::Error>;
}
struct FieldNameStruct {
fields: Vec<String>,
}
impl SerializeStruct for FieldNameStruct {
type Ok = Vec<String>;
type Error = ser::Error;
fn serialize_field<T: ?Sized + ser::Serialize>(
&mut self,
key: &'static str,
_: &T,
) -> Result<(), Self::Error> {
self.fields.push(key.to_string());
Ok(())
}
fn end(self) -> Result<Self::Ok, Self::Error> {
Ok(self.fields)
}
}
#[derive(serde::Serialize)]
struct User {
name: String,
email: String,
age: u32,
}
fn main() {
let user = User {
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
age: 30,
};
let fields = user.serialize(FieldNameSerializer { fields: vec
![] }).unwrap();
println!("Fields: {:?}", fields); // ["name", "email", "age"]
}Implement only struct serialization; Impossible handles other compound types.
Impossible type parameters:
| Parameter | Purpose |
|-----------|---------|
| Ok | Return type for successful serialization |
| Error | Error type for failures |
Common serializer patterns with Impossible:
| Pattern | Methods Implemented | Use Case |
|---------|--------------------|----------|
| Primitive-only | serialize_* for primitives | Simple value serialization |
| String-only | serialize_str | Text output |
| Integer-only | serialize_i*, serialize_u* | Numeric processing |
| Struct-only | serialize_struct, SerializeStruct | Field extraction |
| Side-effect | serialize_unit* | Validation, logging |
Methods that need separate types:
| Compound Type | Associated Type | Trait to Implement |
|---------------|-----------------|-------------------|
| Sequence | SerializeSeq | impl SerializeSeq |
| Tuple | SerializeTuple | impl SerializeTuple |
| Map | SerializeMap | impl SerializeMap |
| Struct | SerializeStruct | impl SerializeStruct |
Key insight: serde::ser::Impossible solves the "impl from hell" problem when implementing the Serializer traitāthe trait requires 30+ methods, but most serializers only handle a subset of types. Rather than manually writing unimplemented!() for each unused method, Impossible<T, E> provides blanket panic implementations that clearly identify which unsupported method was called. This pattern enables focused serializer implementations: a string-only serializer implements just serialize_str, a counting serializer implements only primitives and sequences, and a validation serializer implements only unit types. The associated types (SerializeSeq, SerializeStruct, etc.) also use Impossible for compound types you don't support. The panic behavior is intentional and usefulāit fails fast with clear error messages during testing, ensuring you discover which serialization paths your code exercises rather than silently producing wrong output. Use Impossible whenever you implement Serializer for a narrow use case; implement the full trait only when building general-purpose serializers like JSON or Bincode.