What is the purpose of serde::ser::Impossible type for implementing custom serializers that reject all operations?
serde::ser::Impossible is a helper type that implements all Serializer trait methods but each one returns a custom error, providing a convenient base for implementing serializers that only support a subset of serialization operations. Instead of manually implementing every serializer method to return an error, you embed Impossible and only override the methods you want to support. This ensures that unsupported operations produce meaningful error messages rather than panics or undefined behavior.
The Problem: Implementing Serializer Is Verbose
use serde::ser::{self, Serializer, SerializeStruct, SerializeSeq};
use std::fmt;
// A serializer that ONLY supports serializing strings
// Without Impossible, we'd need to implement ALL trait methods:
struct StringOnlySerializer;
impl Serializer for StringOnlySerializer {
type Ok = String;
type Error = StringOnlyError;
type SerializeSeq = Impossible<StringOnlyError>;
type SerializeTuple = Impossible<StringOnlyError>;
type SerializeTupleStruct = Impossible<StringOnlyError>;
type SerializeTupleVariant = Impossible<StringOnlyError>;
type SerializeMap = Impossible<StringOnlyError>;
type SerializeStruct = Impossible<StringOnlyError>;
type SerializeStructVariant = Impossible<StringOnlyError>;
fn serialize_bool(self, _: bool) -> Result<Self::Ok, Self::Error> {
Err(StringOnlyError("bools not supported".into()))
}
fn serialize_i8(self, _: i8) -> Result<Self::Ok, Self::Error> {
Err(StringOnlyError("i8 not supported".into()))
}
fn serialize_i16(self, _: i16) -> Result<Self::Ok, Self::Error> {
Err(StringOnlyError("i16 not supported".into()))
}
fn serialize_i32(self, _: i32) -> Result<Self::Ok, Self::Error> {
Err(StringOnlyError("i32 not supported".into()))
}
// ... need to implement ALL ~30 methods with similar errors
// This is tedious and error-prone!
}
#[derive(Debug)]
struct StringOnlyError(String);
impl fmt::Display for StringOnlyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::error::Error for StringOnlyError {}
impl ser::Error for StringOnlyError {
fn custom<T: fmt::Display>(msg: T) -> Self {
StringOnlyError(msg.to_string())
}
}The Serializer trait has many methods, and implementing each one to return an error is repetitive.
The Solution: Using Impossible
use serde::ser::{self, Serializer, Impossible};
use std::fmt;
// A serializer that ONLY supports serializing strings
// Using Impossible as the base for unsupported operations
struct StringOnlySerializer;
#[derive(Debug)]
struct StringOnlyError(String);
impl fmt::Display for StringOnlyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::error::Error for StringOnlyError {}
impl ser::Error for StringOnlyError {
fn custom<T: fmt::Display>(msg: T) -> Self {
StringOnlyError(msg.to_string())
}
}
impl Serializer for StringOnlySerializer {
type Ok = String;
type Error = StringOnlyError;
// All compound types return Impossible<Error>
// Impossible implements all the compound serializer traits
// with methods that always return an error
type SerializeSeq = Impossible<StringOnlyError>;
type SerializeTuple = Impossible<StringOnlyError>;
type SerializeTupleStruct = Impossible<StringOnlyError>;
type SerializeTupleVariant = Impossible<StringOnlyError>;
type SerializeMap = Impossible<StringOnlyError>;
type SerializeStruct = Impossible<StringOnlyError>;
type SerializeStructVariant = Impossible<StringOnlyError>;
// Only implement the methods we support:
fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
Ok(v.to_string()) // This works!
}
// All other primitive types will use default implementations
// from serde that return Error::custom("not supported")
// OR we can use Impossible in a different way...
}Actually, let me correct my understanding. Impossible is typically used as the return type for compound serialization types, not as a way to avoid implementing primitive methods.
Impossible as Return Type for Compound Serialization
use serde::ser::{self, Serializer, Impossible, SerializeSeq};
use std::fmt;
// The primary use of Impossible: as the type for unsupported compound types
struct FlatSerializer;
#[derive(Debug)]
struct FlatError(String);
impl fmt::Display for FlatError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::error::Error for FlatError {}
impl ser::Error for FlatError {
fn custom<T: fmt::Display>(msg: T) -> Self {
FlatError(msg.to_string())
}
}
impl Serializer for FlatSerializer {
type Ok = String;
type Error = FlatError;
// This serializer does NOT support sequences
// Impossible implements SerializeSeq, SerializeTuple, etc.
// but all methods return errors
type SerializeSeq = Impossible<FlatError>;
type SerializeTuple = Impossible<FlatError>;
type SerializeTupleStruct = Impossible<FlatError>;
type SerializeTupleVariant = Impossible<FlatError>;
// This serializer does NOT support maps
type SerializeMap = Impossible<FlatError>;
type SerializeStruct = Impossible<FlatError>;
type SerializeStructVariant = Impossible<FlatError>;
// Implement primitive types...
fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
Ok(v.to_string())
}
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())
}
// When serialize_seq is called, it returns Impossible
// Calling any method on Impossible returns an error
fn serialize_seq(self, _: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
// Impossible::new() creates an instance
Ok(Impossible::new(FlatError("sequences not supported".into())))
}
}
fn using_flat_serializer() {
use serde::Serialize;
// This works - simple value
let result = "hello".serialize(&mut FlatSerializer);
assert!(result.is_ok());
// This fails - sequence not supported
let result = vec![1, 2, 3].serialize(&mut FlatSerializer);
assert!(result.is_err());
}Impossible<E> implements all the compound serialization traits but returns errors from every method.
What Impossible Implements
use serde::ser::{Impossible, Error, SerializeSeq, SerializeMap, SerializeStruct};
use std::fmt;
// Impossible<E> implements:
// - SerializeSeq
// - SerializeTuple
// - SerializeTupleStruct
// - SerializeTupleVariant
// - SerializeMap
// - SerializeStruct
// - SerializeStructVariant
// Each trait's methods return Error::custom("...")
fn demonstrate_impossible_traits() {
use std::marker::PhantomData;
#[derive(Debug)]
struct MyError(String);
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::error::Error for MyError {}
impl Error for MyError {
fn custom<T: fmt::Display>(msg: T) -> Self {
MyError(msg.to_string())
}
}
// Create an Impossible instance
let impossible: Impossible<MyError> = Impossible::new(MyError("not supported".into()));
// All methods return errors:
// - serialize_seq: returns Impossible
// - serialize_map: returns Impossible
// - serialize_struct: returns Impossible
// - etc.
}Every compound serialization trait method on Impossible returns an error derived from the provided error value.
Pattern: Unit Type Serialization
use serde::ser::{self, Serializer, Impossible};
use std::fmt;
// Common pattern: Serializer that only accepts unit type ()
struct UnitOnlySerializer;
#[derive(Debug)]
struct UnitOnlyError(String);
impl fmt::Display for UnitOnlyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::error::Error for UnitOnlyError {}
impl ser::Error for UnitOnlyError {
fn custom<T: fmt::Display>(msg: T) -> Self {
UnitOnlyError(msg.to_string())
}
}
impl Serializer for UnitOnlySerializer {
type Ok = ();
type Error = UnitOnlyError;
type SerializeSeq = Impossible<UnitOnlyError>;
type SerializeTuple = Impossible<UnitOnlyError>;
type SerializeTupleStruct = Impossible<UnitOnlyError>;
type SerializeTupleVariant = Impossible<UnitOnlyError>;
type SerializeMap = Impossible<UnitOnlyError>;
type SerializeStruct = Impossible<UnitOnlyError>;
type SerializeStructVariant = Impossible<UnitOnlyError>;
// Only unit is supported
fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
Ok(()) // Success!
}
fn serialize_unit_struct(self, _: &'static str) -> Result<Self::Ok, Self::Error> {
Ok(()) // Unit structs are okay
}
fn serialize_unit_variant(
self,
_: &'static str,
_: u32,
_: &'static str
) -> Result<Self::Ok, Self::Error> {
Ok(()) // Unit variants are okay
}
// All other types should return errors
fn serialize_bool(self, _: bool) -> Result<Self::Ok, Self::Error> {
Err(UnitOnlyError("only unit type supported".into()))
}
fn serialize_i8(self, _: i8) -> Result<Self::Ok, Self::Error> {
Err(UnitOnlyError("only unit type supported".into()))
}
// ... etc for all other primitives
}
fn test_unit_serializer() {
use serde::Serialize;
// Unit works
let result = ().serialize(&mut UnitOnlySerializer);
assert!(result.is_ok());
// Other types fail
let result = 42.serialize(&mut UnitOnlySerializer);
assert!(result.is_err());
}This pattern is useful for serializers that need to reject most types but not all.
Pattern: Rejecting All Serialization
use serde::ser::{self, Impossible};
use std::fmt;
// A type that cannot be serialized at all
// Useful as a component in complex serializer designs
// Impossible itself implements Serializer with all methods returning errors
// So we can use it directly in some contexts
#[derive(Debug)]
struct RejectAllError(String);
impl fmt::Display for RejectAllError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "serialization rejected: {}", self.0)
}
}
impl std::error::Error for RejectAllError {}
impl ser::Error for RejectAllError {
fn custom<T: fmt::Display>(msg: T) -> Self {
RejectAllError(msg.to_string())
}
}
// Impossible<RejectAllError> can be used as a SerializeSeq, etc.
// where all operations on the sequence return the error
fn demonstrate_rejection() {
// Create Impossible instance
let impossible: Impossible<RejectAllError> =
Impossible::new(RejectAllError("no serialization allowed".into()));
// Note: Impossible is typically used internally by serializers
// not directly by users
}Impossible is primarily an implementation detail for custom serializers.
Real Use Case: Deserializer for Skipping Fields
use serde::de::{self, Deserializer, Visitor, Error, MapAccess};
use serde::ser::Impossible;
use std::fmt;
// When implementing a Deserializer, you may need to skip unknown fields
// Impossible can help with the internal "skip" deserializer
// This is more relevant in deserialization contexts
// where you need to consume but ignore certain data
struct SkipDeserializer<E> {
_phantom: std::marker::PhantomData<E>,
}
impl<'de, E> Deserializer<'de> for SkipDeserializer<E>
where
E: de::Error,
{
type Error = E;
// Many methods can be implemented to "skip" data
// But for complex types, you can use related helper types
fn deserialize_any<V>(self, _: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
// Skip by returning unit or similar
Err(E::custom("skipping not implemented"))
}
// ... other methods
}The pattern extends to deserialization contexts as well.
Impossible in Serde's Internal Implementations
use serde::ser::{self, Impossible, SerializeStruct};
// Serde uses Impossible internally for certain adapter types
// For example, when serializing with certain wrappers that
// don't support compound types:
fn serde_internal_usage() {
// Imagine a serializer adapter that only allows strings
// Compound types like structs are serialized but their
// internal serialization returns Impossible
// The serializer returns Impossible for serialize_struct
// When SerializeStruct::end() is called, it returns an error
// This allows the Serialize implementation to follow normal
// struct serialization flow, but reject the operation
// at the point where the struct would actually serialize
}Serde uses Impossible internally for adapters and wrappers.
Common Error Messages
use serde::ser::{self, Impossible};
use std::fmt;
// The error from Impossible is customizable
#[derive(Debug)]
struct TypedError {
message: String,
}
impl fmt::Display for TypedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "TypedError: {}", self.message)
}
}
impl std::error::Error for TypedError {}
impl ser::Error for TypedError {
fn custom<T: fmt::Display>(msg: T) -> Self {
TypedError {
message: msg.to_string(),
}
}
}
fn custom_error_messages() {
// Create Impossible with specific error
let impossible: Impossible<TypedError> = Impossible::new(TypedError {
message: "compound types are not supported by TypedSerializer".into()
});
// When methods are called on this Impossible,
// they return the error provided
// For example, if used as SerializeStruct:
// serialize_field(...) returns Err(TypedError { ... })
// end() returns Err(TypedError { ... })
}The error message is customizable through your error type.
Complete Example: Counting Serializer
use serde::ser::{self, Serializer, Impossible, SerializeStruct};
use std::fmt;
// A serializer that counts how many fields are serialized
// but doesn't actually produce output for compound types
struct CountingSerializer {
count: usize,
}
#[derive(Debug)]
struct CountingError(String);
impl fmt::Display for CountingError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::error::Error for CountingError {}
impl ser::Error for CountingError {
fn custom<T: fmt::Display>(msg: T) -> Self {
CountingError(msg.to_string())
}
}
impl Serializer for &mut CountingSerializer {
type Ok = ();
type Error = CountingError;
// Support structs for counting
type SerializeStruct = CountingStructSerializer;
// Reject other compound types
type SerializeSeq = Impossible<CountingError>;
type SerializeTuple = Impossible<CountingError>;
type SerializeTupleStruct = Impossible<CountingError>;
type SerializeTupleVariant = Impossible<CountingError>;
type SerializeMap = Impossible<CountingError>;
type SerializeStructVariant = Impossible<CountingError>;
fn serialize_struct(
self,
_: &'static str,
_: usize,
) -> Result<Self::SerializeStruct, Self::Error> {
Ok(CountingStructSerializer { count: 0 })
}
fn serialize_str(self, _: &str) -> Result<Self::Ok, Self::Error> {
self.count += 1;
Ok(())
}
fn serialize_i32(self, _: i32) -> Result<Self::Ok, Self::Error> {
self.count += 1;
Ok(())
}
fn serialize_bool(self, _: bool) -> Result<Self::Ok, Self::Error> {
self.count += 1;
Ok(())
}
fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
self.count += 1;
Ok(())
}
// ... other primitives increment count
fn serialize_seq(self, _: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
// Returns Impossible - sequences not supported
Err(CountingError("sequences not supported".into()))
}
}
struct CountingStructSerializer {
count: usize,
}
impl SerializeStruct for CountingStructSerializer {
type Ok = ();
type Error = CountingError;
fn serialize_field<T: ?Sized + ser::Serialize>(
&mut self,
_: &'static str,
_: &T,
) -> Result<(), Self::Error> {
self.count += 1;
Ok(())
}
fn end(self) -> Result<Self::Ok, Self::Error> {
Ok(())
}
}
// Using Impossible for sequences, tuples, etc. means:
// - serialize_seq returns Impossible
// - Any method called on Impossible returns error
// - The error propagates to the callerThis shows how Impossible cleanly rejects unsupported operations.
Practical Guidelines
use serde::ser::{self, Serializer, Impossible};
// When to use Impossible:
// 1. Implementing a serializer that only supports certain compound types
// - Use Impossible for the types you don't support
// 2. Creating adapter serializers that wrap inner serializers
// - Use Impossible when the adapter can't handle a type
// 3. Implementing serializers for restricted formats
// - JSON doesn't need Impossible (supports all types)
// - Custom binary format might not support maps -> use Impossible
// Example: A "string-only" serializer for logging
struct LoggingSerializer;
impl Serializer for LoggingSerializer {
type Ok = String;
type Error = std::fmt::Error;
// Only need string serialization
// Everything else returns errors
type SerializeSeq = Impossible<std::fmt::Error>;
type SerializeTuple = Impossible<std::fmt::Error>;
type SerializeTupleStruct = Impossible<std::fmt::Error>;
type SerializeTupleVariant = Impossible<std::fmt::Error>;
type SerializeMap = Impossible<std::fmt::Error>;
type SerializeStruct = Impossible<std::fmt::Error>;
type SerializeStructVariant = Impossible<std::fmt::Error>;
fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
Ok(v.to_string())
}
fn serialize_seq(self, _: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
// Impossible::new() creates the instance
Ok(Impossible::new(std::fmt::Error))
}
// Other primitives would need implementations that return errors
// or use Impossible patterns
}Use Impossible when your serializer cannot handle certain compound types.
Synthesis
Quick reference:
use serde::ser::{self, Serializer, Impossible};
use std::fmt;
// Impossible<E> is a helper type that:
// 1. Implements all compound serialization traits
// 2. All methods return errors derived from E
// 3. Used as return type for unsupported compound operations
// Example serializer that only supports primitives:
struct PrimitiveOnlySerializer;
#[derive(Debug)]
struct PrimError(String);
impl fmt::Display for PrimError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::error::Error for PrimError {}
impl ser::Error for PrimError {
fn custom<T: fmt::Display>(msg: T) -> Self {
PrimError(msg.to_string())
}
}
impl Serializer for PrimitiveOnlySerializer {
type Ok = String;
type Error = PrimError;
// All compound types return Impossible
type SerializeSeq = Impossible<PrimError>;
type SerializeTuple = Impossible<PrimError>;
type SerializeTupleStruct = Impossible<PrimError>;
type SerializeTupleVariant = Impossible<PrimError>;
type SerializeMap = Impossible<PrimError>;
type SerializeStruct = Impossible<PrimError>;
type SerializeStructVariant = Impossible<PrimError>;
// Primitives work:
fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
Ok(v.to_string())
}
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())
}
// Compound types fail:
fn serialize_seq(self, _: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
// Returns Impossible which will error on any operation
Ok(Impossible::new(PrimError("sequences not supported".into())))
}
fn serialize_struct(
self,
_: &'static str,
_: usize,
) -> Result<Self::SerializeStruct, Self::Error> {
Ok(Impossible::new(PrimError("structs not supported".into())))
}
}
// Key points:
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// β What Impossible provides β
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
// β - Implements SerializeSeq β
// β - Implements SerializeTuple β
// β - Implements SerializeMap β
// β - Implements SerializeStruct β
// β - Implements all variant forms β
// β - All methods return the provided error β
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// When to use:
// β
Implementing serializers for formats with limited type support
// β
Creating adapters that reject certain compound types
// β
Placeholder for unimplemented compound serialization
// β
Type-level documentation that certain types are unsupported
// When NOT to use:
// β When all types should be supported
// β When you need custom behavior (not just rejection)
// β When panics are acceptable (use unimplemented! instead for dev)Key insight: Impossible is serde's solution to the "trait is too large to implement manually" problem. The Serializer trait requires defining associated types for all compound serializers (sequences, tuples, maps, structs, and their variants). When a custom serializer doesn't support certain compound typesβperhaps it only handles primitives or only handles structsβImpossible<E> provides a zero-effort implementation that returns errors for all operations. This allows you to implement a valid Serializer trait without writing dozens of error-returning stub methods. The error type E carries your custom error message, making rejection explicit and debuggable. Without Impossible, you'd need to create your own helper type implementing SerializeStruct, SerializeSeq, etc., each returning errorsβa significant boilerplate burden that serde eliminates with this utility type.
