ExamplesBy LevelBy TopicLearning Paths
289 Intermediate

289: Extending Collections with extend()

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "289: Extending Collections with extend()" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. Building up a collection incrementally from multiple sources — appending new items to an existing `Vec`, merging two `HashMap`s, adding elements from a computation to an existing set — is a fundamental operation in accumulative algorithms. Key difference from OCaml: 1. **Mutability**: Rust's `extend()` mutates the collection in place, consuming the iterator; OCaml's `@` creates a new list.

Tutorial

The Problem

Building up a collection incrementally from multiple sources — appending new items to an existing Vec, merging two HashMaps, adding elements from a computation to an existing set — is a fundamental operation in accumulative algorithms. The extend() method is the mutable counterpart to collect(): it appends elements from any IntoIterator to an existing collection in place, avoiding the need to create intermediate temporary collections.

🎯 Learning Outcomes

  • • Understand extend() as the in-place append operation for any Extend<T> collection
  • • Use extend() to merge multiple sources into a single pre-existing collection
  • • Recognize that extend on a Vec is equivalent to append but accepts any iterator
  • • Combine extend() with filtered or transformed iterators for selective merging
  • Code Example

    let mut base = vec![1, 2, 3];
    base.extend([4, 5, 6]);  // in-place, reuses allocation

    Key Differences

  • Mutability: Rust's extend() mutates the collection in place, consuming the iterator; OCaml's @ creates a new list.
  • Conflict resolution: HashMap extend silently overwrites on key collision; explicit merge logic is needed if different behavior is desired.
  • Allocation efficiency: extend() on a Vec reserves space using size_hint() to minimize reallocations; OCaml's @ always allocates.
  • Build pattern: The common Rust pattern builds a Vec with with_capacity() then uses extend() in a loop — one allocation, multiple appends.
  • OCaml Approach

    OCaml's List.rev_append and @ operator combine lists, but these create new lists rather than mutating in place. For mutable structures, Buffer.add_string (for strings) and Hashtbl.add_seq provide equivalent in-place extension:

    (* Functional: create new list combining both *)
    let combined = base @ [4; 5; 6]
    
    (* Mutable: Hashtbl extend from sequence *)
    Hashtbl.add_seq tbl (List.to_seq [("b", 2); ("c", 3)])
    

    Full Source

    #![allow(clippy::all)]
    //! # Extending Collections with extend()
    //!
    //! `extend()` appends elements from an iterator to an existing collection in place.
    
    use std::collections::{HashMap, HashSet};
    
    /// Extend a vector with elements from another source
    pub fn extend_vec_demo() -> Vec<i32> {
        let mut base = vec![1, 2, 3];
        base.extend([4, 5, 6]);
        base.extend(7..=9);
        base
    }
    
    /// Extend a string with characters
    pub fn extend_string(base: &str, suffix: &str) -> String {
        let mut s = String::from(base);
        s.extend(suffix.chars());
        s
    }
    
    /// Extend a HashMap with new entries
    pub fn extend_hashmap<'a>(
        base: &mut HashMap<&'a str, i32>,
        entries: impl IntoIterator<Item = (&'a str, i32)>,
    ) {
        base.extend(entries);
    }
    
    /// Extend a HashSet (duplicates are ignored)
    pub fn extend_hashset(
        mut set: HashSet<i32>,
        extras: impl IntoIterator<Item = i32>,
    ) -> HashSet<i32> {
        set.extend(extras);
        set
    }
    
    /// Extend with filtered iterator
    pub fn extend_with_filter(
        base: &mut Vec<i32>,
        source: impl Iterator<Item = i32>,
        predicate: impl Fn(&i32) -> bool,
    ) {
        base.extend(source.filter(predicate));
    }
    
    /// Incremental building pattern
    pub fn build_incrementally(batches: &[&[i32]]) -> Vec<i32> {
        let mut result = Vec::new();
        for batch in batches {
            result.extend(*batch);
        }
        result
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_extend_vec_basic() {
            let mut v = vec![1i32, 2, 3];
            v.extend([4, 5, 6]);
            assert_eq!(v, vec![1, 2, 3, 4, 5, 6]);
        }
    
        #[test]
        fn test_extend_vec_demo() {
            let v = extend_vec_demo();
            assert_eq!(v, vec![1, 2, 3, 4, 5, 6, 7, 8, 9]);
        }
    
        #[test]
        fn test_extend_string() {
            let s = extend_string("hello", " world");
            assert_eq!(s, "hello world");
        }
    
        #[test]
        fn test_extend_with_range() {
            let mut v: Vec<i32> = vec![];
            v.extend(1..=5);
            assert_eq!(v, vec![1, 2, 3, 4, 5]);
        }
    
        #[test]
        fn test_extend_hashset_dedup() {
            let set: HashSet<i32> = [1, 2].iter().copied().collect();
            let extended = extend_hashset(set, [2, 3]);
            assert_eq!(extended.len(), 3);
        }
    
        #[test]
        fn test_extend_hashmap() {
            let mut map: HashMap<&str, i32> = HashMap::new();
            map.insert("a", 1);
            extend_hashmap(&mut map, [("b", 2), ("c", 3)]);
            assert_eq!(map.len(), 3);
            assert_eq!(map["b"], 2);
        }
    
        #[test]
        fn test_extend_with_filter() {
            let mut evens = vec![2, 4];
            extend_with_filter(&mut evens, 1..=10, |x| x % 2 == 0);
            assert_eq!(evens, vec![2, 4, 2, 4, 6, 8, 10]);
        }
    
        #[test]
        fn test_build_incrementally() {
            let result = build_incrementally(&[&[1, 2], &[3, 4], &[5, 6]]);
            assert_eq!(result, vec![1, 2, 3, 4, 5, 6]);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_extend_vec_basic() {
            let mut v = vec![1i32, 2, 3];
            v.extend([4, 5, 6]);
            assert_eq!(v, vec![1, 2, 3, 4, 5, 6]);
        }
    
        #[test]
        fn test_extend_vec_demo() {
            let v = extend_vec_demo();
            assert_eq!(v, vec![1, 2, 3, 4, 5, 6, 7, 8, 9]);
        }
    
        #[test]
        fn test_extend_string() {
            let s = extend_string("hello", " world");
            assert_eq!(s, "hello world");
        }
    
        #[test]
        fn test_extend_with_range() {
            let mut v: Vec<i32> = vec![];
            v.extend(1..=5);
            assert_eq!(v, vec![1, 2, 3, 4, 5]);
        }
    
        #[test]
        fn test_extend_hashset_dedup() {
            let set: HashSet<i32> = [1, 2].iter().copied().collect();
            let extended = extend_hashset(set, [2, 3]);
            assert_eq!(extended.len(), 3);
        }
    
        #[test]
        fn test_extend_hashmap() {
            let mut map: HashMap<&str, i32> = HashMap::new();
            map.insert("a", 1);
            extend_hashmap(&mut map, [("b", 2), ("c", 3)]);
            assert_eq!(map.len(), 3);
            assert_eq!(map["b"], 2);
        }
    
        #[test]
        fn test_extend_with_filter() {
            let mut evens = vec![2, 4];
            extend_with_filter(&mut evens, 1..=10, |x| x % 2 == 0);
            assert_eq!(evens, vec![2, 4, 2, 4, 6, 8, 10]);
        }
    
        #[test]
        fn test_build_incrementally() {
            let result = build_incrementally(&[&[1, 2], &[3, 4], &[5, 6]]);
            assert_eq!(result, vec![1, 2, 3, 4, 5, 6]);
        }
    }

    Deep Comparison

    OCaml vs Rust: extend()

    Pattern 1: Append to Collection

    OCaml

    let base = [1; 2; 3] in
    let extension = [4; 5; 6] in
    let combined = base @ extension  (* creates new list *)
    

    Rust

    let mut base = vec![1, 2, 3];
    base.extend([4, 5, 6]);  // in-place, reuses allocation
    

    Pattern 2: String Building

    OCaml

    let buf = Buffer.create 16 in
    Buffer.add_string buf "hello";
    Buffer.add_string buf " world";
    Buffer.contents buf
    

    Rust

    let mut s = String::from("Hello");
    s.extend(", world!".chars());
    // or: s.push_str(", world!");
    

    Pattern 3: Incremental Building

    Rust

    let mut result = Vec::new();
    for batch in batches {
        result.extend(batch);  // efficient - reuses allocation
    }
    

    Key Differences

    AspectOCamlRust
    List append@ creates new listextend modifies in-place
    AllocationAlways newReuses when possible
    StringBuffer.add_stringextend(chars) or push_str
    Mapsfold_left + insertmap.extend(pairs)
    vs chain+collectEquivalent resultextend avoids new allocation

    Exercises

  • Build a word frequency map by extend()-ing a HashMap<String, usize> from multiple document iterators, combining counts correctly.
  • Use extend() to merge sorted chunks back into a single sorted Vec (k-way merge via repeated extend + sort, demonstrating the efficiency concern).
  • Implement a StringBuilder wrapper around String that provides an append(iter) method using extend(), chaining multiple sources.
  • Open Source Repos