How does serde::ser::Serializer::collect_seq simplify serializing iterators without intermediate collections?

collect_seq allows serializers to consume iterators directly without requiring them to be collected into an intermediate container like Vec, enabling more efficient serialization for types that naturally produce sequences through iteration. Instead of forcing every sequence through serialize_seq, which requires upfront knowledge of the length for many serializers, collect_seq lets the serializer decide how to handle the iterator—some serializers can iterate directly without buffering, while others may still need to collect internally. This is particularly valuable when serializing iterators from generators, transformations, or lazy sequences where collecting into a Vec would be wasteful.

Basic Serialization Without collect_seq

use serde::{Serialize, Serializer};
use serde::ser::SerializeSeq;
 
struct Numbers {
    values: Vec<i32>,
}
 
impl Serialize for Numbers {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // Traditional approach: use serialize_seq
        let mut seq = serializer.serialize_seq(Some(self.values.len()))?;
        for value in &self.values {
            seq.serialize_element(value)?;
        }
        seq.end()
    }
}
 
fn main() {
    let nums = Numbers { values: vec![1, 2, 3, 4, 5] };
    let json = serde_json::to_string(&nums).unwrap();
    println!("{}", json);  // [1,2,3,4,5]
}

The traditional serialize_seq approach requires knowing the length upfront.

Using collect_seq

use serde::{Serialize, Serializer};
 
struct Numbers {
    values: Vec<i32>,
}
 
impl Serialize for Numbers {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // collect_seq handles the iterator directly
        serializer.collect_seq(&self.values)
    }
}
 
fn main() {
    let nums = Numbers { values: vec![1, 2, 3, 4, 5] };
    let json = serde_json::to_string(&nums).unwrap();
    println!("{}", json);  // [1,2,3,4,5]
}

collect_seq is simpler when you have an iterable.

Serializing Iterators Without Collecting

use serde::{Serialize, Serializer};
 
// The key benefit: no intermediate Vec allocation
struct TransformedValues {
    values: Vec<i32>,
}
 
impl Serialize for TransformedValues {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // Without collect_seq, you'd need to collect:
        // let transformed: Vec<_> = self.values.iter().map(|x| x * 2).collect();
        // serializer.serialize_seq_iter(transformed.iter())
        
        // With collect_seq, serialize directly from iterator:
        serializer.collect_seq(self.values.iter().map(|x| x * 2))
    }
}
 
fn main() {
    let vals = TransformedValues { values: vec![1, 2, 3] };
    let json = serde_json::to_string(&vals).unwrap();
    println!("{}", json);  // [2,4,6]
}

collect_seq accepts any iterator, avoiding intermediate allocation.

Range and Generator Serialization

use serde::{Serialize, Serializer};
 
struct RangeData {
    start: i32,
    end: i32,
}
 
impl Serialize for RangeData {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // Serialize a range without collecting
        serializer.collect_seq(self.start..self.end)
    }
}
 
fn main() {
    let range = RangeData { start: 1, end: 10 };
    let json = serde_json::to_string(&range).unwrap();
    println!("{}", json);  // [1,2,3,4,5,6,7,8,9]
}

Ranges can be serialized directly without creating a Vec.

Filtering and Transformation

use serde::{Serialize, Serializer};
 
struct FilteredData {
    values: Vec<i32>,
}
 
impl Serialize for FilteredData {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // Serialize only even values, doubled
        // No intermediate Vec needed
        serializer.collect_seq(
            self.values
                .iter()
                .filter(|x| *x % 2 == 0)
                .map(|x| x * 2)
        )
    }
}
 
fn main() {
    let data = FilteredData { values: vec![1, 2, 3, 4, 5, 6] };
    let json = serde_json::to_string(&data).unwrap();
    println!("{}", json);  // [4,8,12]
}

Chain transformations without intermediate collections.

Custom Iterator Types

use serde::{Serialize, Serializer};
use std::iter::Iterator;
 
// Custom iterator that generates values on demand
struct Fibonacci {
    a: u64,
    b: u64,
    remaining: usize,
}
 
impl Fibonacci {
    fn new(n: usize) -> Self {
        Fibonacci { a: 0, b: 1, remaining: n }
    }
}
 
impl Iterator for Fibonacci {
    type Item = u64;
    
    fn next(&mut self) -> Option<Self::Item> {
        if self.remaining == 0 {
            return None;
        }
        self.remaining -= 1;
        let current = self.a;
        let next = self.a + self.b;
        self.a = self.b;
        self.b = next;
        Some(current)
    }
}
 
struct FibonacciSequence {
    count: usize,
}
 
impl Serialize for FibonacciSequence {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // Serialize generated values without storing them all
        serializer.collect_seq(Fibonacci::new(self.count))
    }
}
 
fn main() {
    let fib = FibonacciSequence { count: 10 };
    let json = serde_json::to_string(&fib).unwrap();
    println!("{}", json);  // [0,1,1,2,3,5,8,13,21,34]
}

Custom iterators serialize directly without a backing collection.

How collect_seq Works Internally

use serde::{Serialize, Serializer, ser::SerializeSeq};
 
// collect_seq is implemented on the Serializer trait
// It provides a default implementation that:
// 1. Creates a sequence serializer
// 2. Iterates through the items
// 3. Serializes each element
// 4. Ends the sequence
 
struct MySeq<T> {
    items: Vec<T>,
}
 
impl<T: Serialize> Serialize for MySeq<T> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // The default implementation of collect_seq:
        // fn collect_seq<I>(self, iter: I) -> Result<Self::Ok, Self::Error>
        // where
        //     I: IntoIterator,
        //     I::Item: Serialize,
        // {
        //     let mut seq = self.serialize_seq(None)?;
        //     for item in iter {
        //         seq.serialize_element(&item)?;
        //     }
        //     seq.end()
        // }
        
        // For serde_json, this is optimized to not require length upfront
        serializer.collect_seq(&self.items)
    }
}
 
fn main() {
    let seq = MySeq { items: vec![1, 2, 3] };
    let json = serde_json::to_string(&seq).unwrap();
    println!("{}", json);
}

The default implementation handles the iteration for you.

Serializer-Specific Optimizations

use serde::{Serialize, Serializer};
use serde_json::Serializer as JsonSerializer;
use std::io;
 
// Some serializers optimize collect_seq
// serde_json can write directly to the output without buffering
 
struct StreamingData {
    count: usize,
}
 
impl Serialize for StreamingData {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // For JSON, this writes each element as it comes
        // No need to buffer all elements first
        serializer.collect_seq(0..self.count)
    }
}
 
fn main() {
    let data = StreamingData { count: 5 };
    
    // Write directly to stdout
    let mut writer = io::stdout();
    let mut serializer = JsonSerializer::new(&mut writer);
    data.serialize(&mut serializer).unwrap();
    // Output: [0,1,2,3,4]
}

JSON serializers can stream elements without buffering.

Comparing Approaches

use serde::{Serialize, Serializer, ser::SerializeSeq};
 
struct Data {
    values: Vec<i32>,
}
 
// Approach 1: Manual serialize_seq
impl Serialize for Data {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut seq = serializer.serialize_seq(Some(self.values.len()))?;
        for value in &self.values {
            seq.serialize_element(value)?;
        }
        seq.end()
    }
}
 
struct DataTransformed {
    values: Vec<i32>,
}
 
// Approach 2: collect_seq (simpler)
impl Serialize for DataTransformed {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.collect_seq(self.values.iter().map(|x| x * 2))
    }
}
 
struct DataFiltered {
    values: Vec<i32>,
}
 
// Approach 3: Without collect_seq (requires intermediate Vec)
impl Serialize for DataFiltered {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // This allocates an intermediate Vec
        let filtered: Vec<_> = self.values.iter().filter(|x| *x % 2 == 0).copied().collect();
        serializer.collect_seq(&filtered)
    }
}
 
struct DataFilteredOptimal {
    values: Vec<i32>,
}
 
// Approach 4: With collect_seq (no intermediate allocation)
impl Serialize for DataFilteredOptimal {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // No intermediate Vec - iterator consumed directly
        serializer.collect_seq(self.values.iter().filter(|x| *x % 2 == 0))
    }
}
 
fn main() {
    let data = Data { values: vec![1, 2, 3, 4, 5] };
    let json = serde_json::to_string(&data).unwrap();
    println!("Data: {}", json);
    
    let transformed = DataTransformed { values: vec![1, 2, 3] };
    let json = serde_json::to_string(&transformed).unwrap();
    println!("Transformed: {}", json);  // [2,4,6]
}

collect_seq is cleaner and can avoid intermediate allocations.

Serializing Option Containers

use serde::{Serialize, Serializer};
 
struct OptionalData {
    values: Option<Vec<i32>>,
}
 
impl Serialize for OptionalData {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // Handle Option by serializing contents or empty
        match &self.values {
            Some(v) => serializer.collect_seq(v),
            None => serializer.collect_seq(std::iter::empty::<i32>()),
        }
    }
}
 
// Or use collect_seq with flattened Option
struct OptionalFlattened {
    values: Option<Vec<i32>>,
}
 
impl Serialize for OptionalFlattened {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // Flatten the Option into an iterator
        serializer.collect_seq(self.values.iter().flatten())
    }
}
 
fn main() {
    let some_data = OptionalData { values: Some(vec![1, 2, 3]) };
    let json = serde_json::to_string(&some_data).unwrap();
    println!("Some: {}", json);  // [1,2,3]
    
    let none_data = OptionalData { values: None };
    let json = serde_json::to_string(&none_data).unwrap();
    println!("None: {}", json);  // []
}

collect_seq works with any iterator, including empty ones.

Nested Serialization

use serde::{Serialize, Serializer};
 
struct Matrix {
    rows: usize,
    cols: usize,
    data: Vec<Vec<i32>>,
}
 
impl Serialize for Matrix {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // Collect nested sequences
        serializer.collect_seq(self.data.iter().map(|row| {
            // Each row is also serialized via collect_seq
            serde::ser::SerializeSeq::end(
                // This won't actually work - we need proper nested serialization
                todo!()
            )
        }))
    }
}
 
// Better approach: derive Serialize for inner types
#[derive(serde::Serialize)]
struct Row {
    values: Vec<i32>,
}
 
struct MatrixV2 {
    rows: Vec<Row>,
}
 
impl Serialize for MatrixV2 {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.collect_seq(&self.rows)
    }
}
 
fn main() {
    // Nested serialization naturally works with collect_seq
    let matrix = MatrixV2 {
        rows: vec![
            Row { values: vec![1, 2, 3] },
            Row { values: vec![4, 5, 6] },
        ],
    };
    let json = serde_json::to_string(&matrix).unwrap();
    println!("{}", json);  // [[1,2,3],[4,5,6]]
}

Nested structures work naturally with collect_seq.

Memory Efficiency Comparison

use serde::{Serialize, Serializer};
 
// Scenario: Serializing a large filtered dataset
struct LargeData {
    items: Vec<i32>,
}
 
impl Serialize for LargeData {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // Without collect_seq, you'd need:
        // let filtered: Vec<_> = self.items.iter().filter(|x| expensive_check(x)).collect();
        // This allocates memory proportional to matching items
        
        // With collect_seq:
        serializer.collect_seq(
            self.items
                .iter()
                .filter(|x| *x % 1000 == 0)  // Keep every 1000th
        )
        // No intermediate Vec allocated
    }
}
 
// Even better: generator pattern
struct GeneratedSequence {
    count: usize,
}
 
impl Serialize for GeneratedSequence {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // Generate on demand - no storage at all
        serializer.collect_seq((0..self.count).map(|i| i * i))
    }
}
 
fn main() {
    let gen = GeneratedSequence { count: 1000 };
    let json = serde_json::to_string(&gen).unwrap();
    println!("First 1000 squares serialized");
}

collect_seq enables memory-efficient serialization for generated data.

Practical Example: Pagination

use serde::{Serialize, Serializer};
 
struct Page<T> {
    items: Vec<T>,
    total: usize,
    page: usize,
    per_page: usize,
}
 
// When you need just the items:
impl<T: Serialize> Serialize for Page<T> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // Simple case: serialize items directly
        serializer.collect_seq(&self.items)
    }
}
 
// If you need more control over the structure:
struct PageMetadata<T> {
    items: Vec<T>,
    total: usize,
    page: usize,
}
 
impl<T: Serialize> Serialize for PageMetadata<T> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        use serde::ser::SerializeStruct;
        
        let mut s = serializer.serialize_struct("Page", 3)?;
        s.serialize_field("total", &self.total)?;
        s.serialize_field("page", &self.page)?;
        // Use collect_seq for the items
        s.serialize_field("items", &self.items)?;
        s.end()
    }
}
 
fn main() {
    let page = Page {
        items: vec![1, 2, 3],
        total: 100,
        page: 1,
        per_page: 10,
    };
    let json = serde_json::to_string(&page).unwrap();
    println!("{}", json);  // [1,2,3]
}

collect_seq works well for pagination and streaming scenarios.

Complete Example: Lazy Evaluation

use serde::{Serialize, Serializer};
use std::collections::HashMap;
 
struct ConfigMap {
    entries: HashMap<String, String>,
}
 
impl Serialize for ConfigMap {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // Serialize key-value pairs
        serializer.collect_seq(
            self.entries
                .iter()
                .map(|(k, v)| format!("{}={}", k, v))
        )
    }
}
 
// Serialize computed values
struct ComputedStats {
    values: Vec<f64>,
}
 
impl Serialize for ComputedStats {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // Compute statistics on demand during serialization
        let mean = self.values.iter().sum::<f64>() / self.values.len() as f64;
        let variance = self.values.iter()
            .map(|x| (x - mean).powi(2))
            .sum::<f64>() / self.values.len() as f64;
        let std_dev = variance.sqrt();
        
        // Serialize computed stats without intermediate struct
        serializer.collect_seq([
            ("mean", mean),
            ("variance", variance),
            ("std_dev", std_dev),
        ].iter())
    }
}
 
fn main() {
    let config = ConfigMap {
        entries: vec![
            ("key1".to_string(), "value1".to_string()),
            ("key2".to_string(), "value2".to_string()),
        ].into_iter().collect(),
    };
    let json = serde_json::to_string(&config).unwrap();
    println!("{}", json);
    
    let stats = ComputedStats {
        values: vec![1.0, 2.0, 3.0, 4.0, 5.0],
    };
    let json = serde_json::to_string(&stats).unwrap();
    println!("{}", json);
}

Computed values serialize without intermediate structures.

Synthesis

Quick reference:

use serde::Serializer;
 
// Basic usage: serialize any iterable
serializer.collect_seq(&vec)              // Vec reference
serializer.collect_seq(iterable)          // Any IntoIterator
serializer.collect_seq(0..10)             // Range
serializer.collect_seq(iter.map(f))        // Transformed iterator
serializer.collect_seq(iter.filter(p))     // Filtered iterator
 
// Traditional serialize_seq requires:
// 1. Explicit sequence start
// 2. Element-by-element serialization
// 3. Explicit end
 
let mut seq = serializer.serialize_seq(Some(len))?;
for item in &items {
    seq.serialize_element(item)?;
}
seq.end()?;
 
// collect_seq handles all of this internally:
serializer.collect_seq(&items)
 
// Key benefits:
// 1. No intermediate Vec allocation
// 2. Simpler code
// 3. Works with any iterator
// 4. Memory efficient for generated/lazy data
// 5. Natural composition with iterator adapters
 
// When to use collect_seq:
// - Serializing transformed/filtered data
// - Generator-style data production
// - Large datasets where allocation matters
// - Simple iterable serialization
 
// When serialize_seq might be better:
// - Need specific length hint for the serializer
// - Custom serialization logic per element
// - Need to handle errors during iteration

Key insight: collect_seq bridges the gap between Rust's iterator ecosystem and serde's serialization model. Without it, serializing transformed or filtered data requires collecting into a Vec first—an unnecessary allocation when the serializer just needs to iterate over the elements. collect_seq accepts any IntoIterator, allowing you to chain map, filter, take, and other iterator adapters directly into the serialization call. The default implementation creates a sequence and serializes each element, but some serializers optimize this further—for example, serde_json can write each element directly to the output stream without buffering. Use collect_seq whenever you're serializing anything that naturally produces values through iteration rather than already storing them.