Loading page…
Rust walkthroughs
Loading page…
serde::ser::SerializeSeq and SerializeTuple for sequence serialization?serde::ser::SerializeSeq and SerializeTuple represent semantically different sequence types in serde's data model: SerializeSeq serializes sequences whose length may not be known until iteration completes and whose elements are homogeneous, while SerializeTuple serializes fixed-length sequences with positionally-significant elements that may have different types. When implementing a Serializer, calling serialize_seq returns a SerializeSeq type that you call serialize_element on repeatedly, then end when done, while serialize_tuple returns a SerializeTuple type where each element is serialized positionally with serialize_element before calling end. The distinction matters for formats that encode these differently—JSON represents both as arrays, but other formats like bincode or message pack may encode tuples differently from sequences because tuples have known, fixed lengths. When implementing Serialize for a custom type, use serialize_seq for dynamically-sized collections like Vec and serialize_tuple for fixed-size heterogeneous collections like tuples themselves or structs with named fields that can be serialized positionally.
use serde::ser::{Serialize, Serializer, SerializeSeq, SerializeTuple};
// In serde's data model, these are different:
// - SerializeSeq: "a sequence of unknown length with homogeneous elements"
// - SerializeTuple: "a fixed-length sequence with heterogeneous elements"
// Examples in the data model:
// - Vec<T> -> SerializeSeq (unknown length, all T)
// - [T; N] -> SerializeSeq (known length, all T)
// - (A, B, C) -> SerializeTuple (fixed length, types A, B, C)
// - struct Foo { a: A, b: B } -> SerializeTuple (fixed, types A and B)
fn main() {
println!("SerializeSeq for dynamic collections");
println!("SerializeTuple for fixed heterogeneous data");
}The serde data model distinguishes sequences from tuples based on length knowledge and element homogeneity.
use serde::ser::{Serializer, SerializeSeq, Serialize};
use std::fmt;
// Custom serializer that shows what happens
struct PrintSerializer;
impl Serializer for PrintSerializer {
type Ok = ();
type Error = std::io::Error;
// SerializeSeq is returned for sequences
type SerializeSeq = PrintSeq;
type SerializeTuple = PrintTuple;
// ... other associated types
fn serialize_seq(self, len: Option<usize>) -> Result<PrintSeq, Self::Error> {
println!("Starting sequence, expected length: {:?}", len);
Ok(PrintSeq { count: 0 })
}
fn serialize_tuple(self, len: usize) -> Result<PrintTuple, Self::Error> {
println!("Starting tuple with {} elements", len);
Ok(PrintTuple { index: 0, len })
}
// Other required methods omitted for brevity
fn serialize_bool(self, _: bool) -> Result<Self::Ok, Self::Error> { Ok(()) }
fn serialize_i8(self, _: i8) -> Result<Self::Ok, Self::Error> { Ok(()) }
fn serialize_i64(self, v: i64) -> Result<Self::Ok, Self::Error> {
println!("Serializing i64: {}", v);
Ok(())
}
fn serialize_u64(self, _: u64) -> Result<Self::Ok, Self::Error> { Ok(()) }
fn serialize_str(self, _: &str) -> Result<Self::Ok, Self::Error> { Ok(()) }
fn serialize_unit(self) -> Result<Self::Ok, Self::Error> { Ok(()) }
fn serialize_none(self) -> Result<Self::Ok, Self::Error> { Ok(()) }
fn serialize_some<T: ?Sized + Serialize>(self, _: &T) -> Result<Self::Ok, Self::Error> { Ok(()) }
fn serialize_unit_struct(self, _: &'static str) -> Result<Self::Ok, Self::Error> { Ok(()) }
fn serialize_unit_variant(self, _: &'static str, _: u32, _: &'static str) -> Result<Self::Ok, Self::Error> { Ok(()) }
fn serialize_newtype_struct<T: ?Sized + Serialize>(self, _: &'static str, _: &T) -> Result<Self::Ok, Self::Error> { Ok(()) }
fn serialize_newtype_variant<T: ?Sized + Serialize>(self, _: &'static str, _: u32, _: &'static str, _: &T) -> Result<Self::Ok, Self::Error> { Ok(()) }
type SerializeTupleStruct = PrintTuple;
fn serialize_tuple_struct(self, _: &'static str, len: usize) -> Result<PrintTuple, Self::Error> {
Ok(PrintTuple { index: 0, len })
}
type SerializeTupleVariant = PrintTuple;
fn serialize_tuple_variant(self, _: &'static str, _: u32, _: &'static str, len: usize) -> Result<PrintTuple, Self::Error> {
Ok(PrintTuple { index: 0, len })
}
type SerializeMap = PrintMap;
fn serialize_map(self, _: Option<usize>) -> Result<PrintMap, Self::Error> {
Ok(PrintMap { count: 0 })
}
type SerializeStruct = PrintStruct;
fn serialize_struct(self, _: &'static str, _: usize) -> Result<PrintStruct, Self::Error> {
Ok(PrintStruct { count: 0 })
}
type SerializeStructVariant = PrintStruct;
fn serialize_struct_variant(self, _: &'static str, _: u32, _: &'static str, _: usize) -> Result<PrintStruct, Self::Error> {
Ok(PrintStruct { count: 0 })
}
}
struct PrintSeq {
count: usize,
}
impl SerializeSeq for PrintSeq {
type Ok = ();
type Error = std::io::Error;
fn serialize_element<T: ?Sized + Serialize>(&mut self, value: &T) -> Result<(), Self::Error> {
self.count += 1;
println!("Sequence element {}: ", self.count);
value.serialize(PrintSerializer)
}
fn end(self) -> Result<Self::Ok, Self::Error> {
println!("Ended sequence with {} elements", self.count);
Ok(())
}
}
struct PrintTuple {
index: usize,
len: usize,
}
impl serde::ser::SerializeTuple for PrintTuple {
type Ok = ();
type Error = std::io::Error;
fn serialize_element<T: ?Sized + Serialize>(&mut self, value: &T) -> Result<(), Self::Error> {
self.index += 1;
println!("Tuple element {}/{}: ", self.index, self.len);
value.serialize(PrintSerializer)
}
fn end(self) -> Result<Self::Ok, Self::Error> {
println!("Ended tuple");
Ok(())
}
}
struct PrintMap { count: usize }
impl serde::ser::SerializeMap for PrintMap {
type Ok = ();
type Error = std::io::Error;
fn serialize_key<T: ?Sized + Serialize>(&mut self, _: &T) -> Result<(), Self::Error> { Ok(()) }
fn serialize_value<T: ?Sized + Serialize>(&mut self, _: &T) -> Result<(), Self::Error> { Ok(()) }
fn end(self) -> Result<Self::Ok, Self::Error> { Ok(()) }
}
struct PrintStruct { count: usize }
impl serde::ser::SerializeStruct for PrintStruct {
type Ok = ();
type Error = std::io::Error;
fn serialize_field<T: ?Sized + Serialize>(&mut self, _: &'static str, _: &T) -> Result<(), Self::Error> { Ok(()) }
fn end(self) -> Result<Self::Ok, Self::Error> { Ok(()) }
}SerializeSeq has a method to serialize elements one at a time, then end.
use serde::{Serialize, Serializer};
impl<T: Serialize> Serialize for MyVec<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// Vec -> sequence
// Length is known, but elements are homogeneous
let mut seq = serializer.serialize_seq(Some(self.items.len()))?;
for item in &self.items {
seq.serialize_element(item)?;
}
seq.end()
}
}
struct MyVec<T> {
items: Vec<T>,
}
fn main() {
// When serializing Vec, we use serialize_seq
// Because:
// 1. Elements are all the same type
// 2. Length is known but semantically a "sequence"
println!("Vec serializes as SerializeSeq");
}Use serialize_seq for collections like Vec, LinkedList, HashSet.
use serde::ser::{Serialize, Serializer, SerializeTuple};
use std::marker::PhantomData;
// Tuple types serialize with SerializeTuple
struct Tuple2<A, B>(A, B);
impl<A: Serialize, B: Serialize> Serialize for Tuple2<A, B> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// Tuple -> fixed-length sequence
// Length is ALWAYS known and fixed
// Elements may have different types
let mut tuple = serializer.serialize_tuple(2)?;
tuple.serialize_element(&self.0)?;
tuple.serialize_element(&self.1)?;
tuple.end()
}
}
fn main() {
// Tuples use serialize_tuple because:
// 1. Length is known at compile time
// 2. Each position has a specific type
// 3. Order matters (position is significant)
println!("Tuples serialize as SerializeTuple");
}Use serialize_tuple for actual tuples (A, B, C) and similar fixed-size heterogeneous types.
use serde::ser::{Serializer, SerializeSeq, SerializeTuple};
fn main() {
// SerializeSeq::serialize_seq
// - Takes Option<usize> for length
// - Length may be None (unknown until iteration)
// - Returns SerializeSeq type
// SerializeTuple::serialize_tuple
// - Takes usize (not Option) for length
// - Length is ALWAYS known
// - Returns SerializeTuple type
// SerializeSeq methods:
// - serialize_element<T>(&mut self, &T)
// - end(self) -> Result<Ok, Error>
// SerializeTuple methods:
// - serialize_element<T>(&mut self, &T)
// - end(self) -> Result<Ok, Error>
// The method names are the same!
// But the semantic meaning differs
println!("API surface similar, semantics different");
}Both have serialize_element and end, but the length parameter differs.
use serde::{Serialize, Serializer};
use serde_json;
fn main() {
// JSON: Both sequences and tuples become arrays
// JSON doesn't distinguish between them
let vec = vec![1, 2, 3];
let tuple = (1, "hello", true);
println!("Vec as JSON: {}", serde_json::to_string(&vec).unwrap());
// [1,2,3]
println!("Tuple as JSON: {}", serde_json::to_string(&tuple).unwrap());
// [1,"hello",true]
// Both look the same in JSON!
// But other formats encode them differently
// Bincode encodes:
// - Sequence: length prefix + elements
// - Tuple: just elements (length known from schema)
// MessagePack encodes:
// - Sequence: array header with length
// - Tuple: array header with length (same representation)
// The difference matters for self-describing formats
// and when the format has different encodings
}JSON represents both as arrays, but binary formats may differ.
use serde::ser::{Serialize, Serializer, SerializeSeq};
use std::collections::HashMap;
struct LazySequence<T> {
items: Vec<T>,
}
impl<T: Serialize> Serialize for LazySequence<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// For sequences, length CAN be None
// Some formats can handle unknown length
let mut seq = serializer.serialize_seq(None)?; // None = unknown length
for item in &self.items {
seq.serialize_element(item)?;
}
seq.end()
}
}
// For tuples, length MUST be known
// serialize_tuple takes usize, not Option<usize>
// This is because tuples are statically sized
fn main() {
println!("Sequences: length can be unknown");
println!("Tuples: length always known");
}serialize_seq takes Option<usize> because length might be unknown.
use serde::{Serialize, Serializer, SerializeTupleStruct};
// Structs can serialize as tuples (tuple structs)
#[derive(Serialize)]
struct Point {
x: f64,
y: f64,
}
// Normally serializes as: {"x": 1.0, "y": 2.0}
// But can serialize as tuple: (1.0, 2.0)
impl Serialize for Point {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// Option 1: Serialize as struct (with field names)
// use serializer.serialize_struct("Point", 2)
// Option 2: Serialize as tuple struct (positional)
// This uses SerializeTupleStruct, similar to SerializeTuple
let mut ts = serializer.serialize_tuple_struct("Point", 2)?;
ts.serialize_field(&self.x)?;
ts.serialize_field(&self.y)?;
ts.end()
}
}
fn main() {
let point = Point { x: 1.0, y: 2.0 };
// Serializes as tuple struct
println!("Point: {:?}", serde_json::to_string(&point));
// [1.0, 2.0] in JSON (array without field names)
}Tuple structs use SerializeTupleStruct, which is similar to SerializeTuple.
use serde::Serialize;
fn main() {
// SerializeSeq: Homogeneous elements
// All elements have the same type
let vec: Vec<i32> = vec![1, 2, 3];
// Elements are all i32 -> SerializeSeq
// SerializeTuple: Heterogeneous elements
// Each position has its own type
let tuple: (i32, &str, bool) = (42, "hello", true);
// Positions: i32, &str, bool -> SerializeTuple
// The distinction matters for type information:
// - Sequence elements share serialization logic
// - Tuple elements serialize independently
// For deserialization:
// - Sequence: visitor expects all elements of same type
// - Tuple: visitor expects specific type at each position
println!("Sequences: homogeneous elements");
println!("Tuples: heterogeneous elements (or positionally significant)");
}Sequences imply homogeneous types; tuples allow heterogeneous types.
use serde::ser::{Serializer, SerializeSeq, SerializeTuple, Serialize};
use std::io::{self, Write};
// A serializer that encodes differently
struct BinarySerializer<W: Write> {
writer: W,
}
impl<W: Write> Serializer for BinarySerializer<W> {
type Ok = ();
type Error = io::Error;
type SerializeSeq = BinarySeq<W>;
type SerializeTuple = BinaryTuple<W>;
// ... other types
fn serialize_seq(self, len: Option<usize>) -> Result<BinarySeq<W>, io::Error> {
// Write length prefix for sequences
if let Some(l) = len {
self.writer.write_all(&(l as u64).to_le_bytes())?;
} else {
// Unknown length: use streaming format
self.writer.write_all(&u64::MAX.to_le_bytes())?;
}
Ok(BinarySeq { writer: self.writer, count: 0 })
}
fn serialize_tuple(self, _len: usize) -> Result<BinaryTuple<W>, io::Error> {
// Tuples: NO length prefix, length is known from schema
Ok(BinaryTuple { writer: self.writer })
}
// ... other methods
}
struct BinarySeq<W: Write> {
writer: W,
count: u64,
}
impl<W: Write> SerializeSeq for BinarySeq<W> {
type Ok = ();
type Error = io::Error;
fn serialize_element<T: ?Sized + Serialize>(&mut self, value: &T) -> Result<(), io::Error> {
self.count += 1;
value.serialize(BinarySerializer { writer: &mut self.writer })
}
fn end(self) -> Result<(), io::Error> {
// Optionally write end marker for unknown-length sequences
Ok(())
}
}
struct BinaryTuple<W: Write> {
writer: W,
}
impl<W: Write> SerializeTuple for BinaryTuple<W> {
type Ok = ();
type Error = io::Error;
fn serialize_element<T: ?Sized + Serialize>(&mut self, value: &T) -> Result<(), io::Error> {
value.serialize(BinarySerializer { writer: &mut self.writer })
}
fn end(self) -> Result<(), io::Error> {
Ok(())
}
}
fn main() {
// Binary format difference:
// Sequence [1, 2, 3]: <length:8bytes><1><2><3>
// Tuple (1, 2, 3): <1><2><3>
//
// Tuples don't need length prefix because the deserializer
// knows how many elements to expect from the schema
}Binary formats can optimize tuple serialization by skipping length prefixes.
fn main() {
// | Aspect | SerializeSeq | SerializeTuple |
// |------------------|---------------------------|---------------------------|
// | Entry method | serialize_seq(len: Option<usize>) | serialize_tuple(len: usize) |
// | Length knowledge | May be unknown | Always known |
// | Element types | Homogeneous | May differ per position |
// | Typical types | Vec, HashSet, slice | (A, B, C), tuple structs |
// | JSON output | Array | Array (same) |
// | Binary output | Length prefix + elements | Elements only |
// | Deserialization | VisitSeq | VisitTuple |
println!("Comparison table above");
}When to use each:
| Data Type | Serialization Method | Length Parameter |
|-----------|---------------------|------------------|
| Vec | serialize_seq | Some(len) |
| [T; N] | serialize_seq | Some(N) |
| HashSet | serialize_seq | Some(len) |
| (A, B, C) | serialize_tuple | 3 (not Optional) |
| Struct (as tuple) | serialize_tuple_struct | N |
| Iterator | serialize_seq | None (unknown) |
Key distinctions:
| Property | SerializeSeq | SerializeTuple |
|----------|---------------|------------------|
| Length | Option<usize> | usize (required) |
| Types | Homogeneous | Heterogeneous possible |
| Semantics | "Collection of items" | "Fixed structure" |
| Deserialization visitor | visit_seq | visit_tuple |
Key insight: SerializeSeq and SerializeTuple represent two philosophies of what a sequence is: SerializeSeq treats a sequence as a dynamic collection whose length is discovered at runtime and whose elements share a type, while SerializeTuple treats a sequence as a static structure whose shape is known at compile time and whose elements may differ in meaning. This distinction exists because some serialization formats can optimize based on this information—binary protocols don't need to write length prefixes for tuples if both serializer and deserializer agree on the schema, and self-describing formats like JSON can still represent both as arrays while preserving the semantic difference. When implementing Serialize for your own types, the question is: is this a container of same-typed items whose count varies, or is this a fixed structure where each position has a specific role? A Vec<String> is clearly a sequence. A tuple (i32, String, bool) is clearly a tuple. But what about a custom coordinate type with x, y, z fields? You could serialize it as a struct with named fields, as a tuple (x, y, z), or even as a sequence. The choice depends on what your deserializer expects and what your format encodes efficiently. The serde data model gives you the vocabulary to express this choice, and the SerializeSeq/SerializeTuple distinction ensures that the choice is communicated through the serialization layer.