What is the purpose of 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.

The Serializer Trait Requirements

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.

Basic Impossible Usage

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.

Without Impossible: Manual Implementation

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.

Integer-Only Serializer

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.

Impossible Type Parameters

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.

Sequence Serialization 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.

Test Double Pattern

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.

Custom Error Types

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.

Handling Option Types

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.

Unit Type Serialization

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.

Panic Messages from Impossible

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.

Struct Serialization with Impossible

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.

Synthesis

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.