Rust Mutable Iterator example

Date: 2025-06-08
pub trait Explainable {
    fn do_explain(&self)-> String;
}

pub trait HasAccount {
    fn get_account(&self)-> String;
    fn change_account(&mut self, new_value: &str);
}

pub struct Person {
    name: String,
    account: String
}

impl Explainable for Person {
    fn do_explain(&self)-> String {
        return self.name.clone() + " " + self.account.as_str()
    }
}

impl HasAccount for Person {
    fn change_account(&mut self, new_value: &str) {
        self.account = new_value.to_string();
    }
    
    fn get_account(&self)-> String {
        return self.account.clone();
    }
}

fn change_accounts<'a>(persons: impl Iterator<Item = &'a mut Person>) {
    for p in persons {
        println!("Changing person: {}", p.name);
        let mut new_str= String::with_capacity(100);
        new_str.push_str(&p.account);
        new_str.push_str("_020");
        p.change_account(&new_str);
    }
}

fn change_accounts2<'a>(persons: impl Iterator<Item = &'a mut dyn HasAccount>) {
    for p in persons {
        let mut new_str = String::with_capacity(100);
        new_str.push_str(&p.get_account());
        new_str.push_str("_020");
        p.change_account(&new_str);
    }
}

fn change_accounts3<'a, I>(persons: I)
where
    I: IntoIterator<Item = &'a mut dyn HasAccount>,
{
    for p in persons {
        let mut new_str = String::with_capacity(100);
        new_str.push_str(&p.get_account());
        new_str.push_str("_020");
        p.change_account(&new_str);
    }
}

fn change_accounts4<'a, I>(persons: I)
where
    I: IntoIterator<Item = &'a mut Box<dyn HasAccount>>,
{
    for p in persons {
        let mut new_str = String::with_capacity(100);
        new_str.push_str(&p.get_account());
        new_str.push_str("_020");
        p.change_account(&new_str);
    }
}

// Trait with both traits
trait Entity: Explainable + HasAccount {}
impl<T: Explainable + HasAccount> Entity for T {}


fn explain_all<'a>(items: impl IntoIterator<Item = &'a Box<dyn Entity>>) {
    for item in items {
        println!("{}", item.do_explain());
    }
}

fn change_all<'a>(items: impl IntoIterator<Item = &'a mut Box<dyn Entity>>) {
    for item in items {
        let mut new_str = item.get_account();
        new_str.push_str("_mod");
        item.change_account(&new_str);
    }
}


fn get_boxed_entities1() -> Vec<Box<dyn Entity>> {
    vec![
        Box::new(Person { name: "Piet".into(), account: "abc".into() }),
        Box::new(Person { name: "Klaas".into(), account: "xyz".into() }),
    ]
}

// return an iterator
fn get_boxed_entities2() -> impl Iterator<Item = Box<dyn Entity>> {
    return vec![
        Box::new(Person { name: "Piet".into(), account: "abc".into() }),
        Box::new(Person { name: "Klaas".into(), account: "xyz".into() }),
    ]
    .into_iter()
    .map(|p| p as Box<dyn Entity>)
}



fn main() {
    println!("Hello, world!");

    let mut persons = vec![
        Person { name: "Jan".into(), account: "2345234".into() },
        Person { name: "Kees".into(), account: "342523".into() }
    ];

    for p in persons.iter() { // <- using .iter() here to allow multiple loops
        let s = p.do_explain();
        println!("{}", s);
    }

    for p in persons.iter_mut() {
        let new_str= p.account.clone() + "_010";
        p.change_account(&new_str);
    }

    change_accounts(persons.iter_mut());
    change_accounts(persons.iter_mut());

    change_accounts2(persons.iter_mut().map(|p| p as &mut dyn HasAccount));

    change_accounts3(persons.iter_mut().map(|p| p as &mut dyn HasAccount));


    for p in persons.iter() {
        let s = p.do_explain();
        println!("{}", s);
    }

    // step 2
    
    let mut boxed: Vec<Box<dyn HasAccount>> = vec![
        Box::new(Person { name: "Piet".into(), account: "abc".into() }),
        Box::new(Person { name: "Klaas".into(), account: "xyz".into() }),
    ];

    change_accounts4(boxed.iter_mut()); //boxed.iter_mut().map(|p| p.as_mut())

    // step 3

    let mut list: Vec<Box<dyn Entity>> = vec![
        Box::new(Person { name: "Jan".into(), account: "123".into() }),
        Box::new(Person { name: "Kees".into(), account: "456".into() }),
    ];

    // read
    explain_all(list.iter()); //.map(|x| x.as_ref())

    // write
    let iter = list.iter_mut(); //.map(|x| x.as_mut())
    change_all(iter);

    // read again
    explain_all(list.iter()); //.map(|x| x.as_ref())


    let mut list2 = get_boxed_entities1();
    explain_all(list2.iter()); 
    let iter = list2.iter_mut();
    change_all(iter);
    explain_all(list2.iter());


    let mut list3: Vec<Box<dyn Entity>> = get_boxed_entities2().collect();
    explain_all(list3.iter()); 
    change_all(list3.iter_mut());
    explain_all(list3.iter());

    println!("Finished");
}
trait IntoBoxedA<'a>: IntoIterator<Item = &'a Box<dyn A>> {}
impl<'a, T> IntoBoxedA<'a> for T where T: IntoIterator<Item = &'a Box<dyn A>> {}


// 3 variants with the same behaviour:

fn process1<'a, T: IntoBoxedA<'a>>(items: T) {
    ...
}

fn process1<'a, T>(items: T)
where
    T: IntoBoxedA<'a>
{
    ...
}

fn process1<'a>(items: impl IntoBoxedA<'a>) {
    ...
}



πŸ‘‰ Kies impl Trait voor korte functies, en de T: Trait-vorm als je meerdere trait bounds of het concrete generieke type nodig hebt.

Per trait a concrete alias

trait IntoBoxedA<'a>: IntoIterator<Item = &'a Box<dyn A>> {}
impl<'a, T> IntoBoxedA<'a> for T where T: IntoIterator<Item = &'a Box<dyn A>> {}

trait IntoBoxedB<'a>: IntoIterator<Item = &'a Box<dyn B>> {}
impl<'a, T> IntoBoxedB<'a> for T where T: IntoIterator<Item = &'a Box<dyn B>> {}

trait IntoBoxedC<'a>: IntoIterator<Item = &'a Box<dyn C>> {}
impl<'a, T> IntoBoxedC<'a> for T where T: IntoIterator<Item = &'a Box<dyn C>> {}


fn process1<'a, T: IntoBoxedA<'a>>(items: T) {
    ...
}

fn process2<'a, T: IntoBoxedB<'a>>(items: T) {
    ...
}


macro_rules! define_into_boxed {
    ($name:ident, $trait:path) => {
        pub trait $name<'a>: IntoIterator<Item = &'a Box<dyn $trait>> {}
        impl<'a, T> $name<'a> for T
        where
            T: IntoIterator<Item = &'a Box<dyn $trait>>,
        {}
    };
}

trait A {
    fn do_a(&self);
}

trait B {
    fn do_b(&self);
}

trait C {
    fn do_c(&self);
}

define_into_boxed!(IntoBoxedA, A);
define_into_boxed!(IntoBoxedB, B);
define_into_boxed!(IntoBoxedC, C);

fn process1<'a>(items: impl IntoBoxedA<'a>) {
    for a in items {
        a.do_a();
    }
}

fn process2<'a>(items: impl IntoBoxedB<'a>) {
    for b in items {
        b.do_b();
    }
}


Limetime errors

// error[E0597]: `boxed` does not live long enough
// error[E0759]: `boxed` captured by a closure, but it is dropped before the closure is called


let mut boxed: Vec<Box<dyn HasAccount>> = vec![
    Box::new(Person { name: "Piet".into(), account: "abc".into() }),
    Box::new(Person { name: "Klaas".into(), account: "xyz".into() }),
];

edit_accounts(boxed.iter_mut().map(|p| p.as_mut()));

// solution: collect into a Vec, this causes the iterator to be used

let mut boxed: Vec<Box<dyn HasAccount>> = vec![
    Box::new(Person { name: "Piet".into(), account: "abc".into() }),
    Box::new(Person { name: "Klaas".into(), account: "xyz".into() }),
];

let mapped: Vec<_> = boxed.iter_mut().map(|p| p.as_mut()).collect();
edit_accounts(mapped);

// link original variable lifetime to iterator:
fn make_iter<'a>(
    boxed: &'a mut [Box<dyn HasAccount>]
) -> impl Iterator<Item = &'a mut dyn HasAccount> {
    boxed.iter_mut().map(|b| b.as_mut())
}


// Full example:

trait HasAccount {
    fn set_account(&mut self, account: &str);
}

struct Person {
    name: String,
    account: String,
}

impl HasAccount for Person {
    fn set_account(&mut self, account: &str) {
        self.account = account.to_string();
    }
}

fn make_iter<'a>(
    boxed: &'a mut [Box<dyn HasAccount>]
) -> impl Iterator<Item = &'a mut dyn HasAccount> {
    boxed.iter_mut().map(|b| b.as_mut())
}

fn edit_accounts<'a, I>(accounts: I)
where
    I: Iterator<Item = &'a mut dyn HasAccount>,
{
    for a in accounts {
        a.set_account("updated");
    }
}

fn main() {
    let mut boxed: Vec<Box<dyn HasAccount>> = vec![
        Box::new(Person { name: "Piet".into(), account: "abc".into() }),
        Box::new(Person { name: "Klaas".into(), account: "xyz".into() }),
    ];

    let iter = make_iter(&mut boxed);
    edit_accounts(iter);
}




Service returning array/vec

Goede vraag β€” dit raakt precies de ownership-filosofie van Rust πŸ‘
Het korte antwoord is: nee, je hoeft meestal géén lijst mee te geven om te vullen.
De meest idiomatische manier is dat je functie zelf de data maakt en deze teruggeeft, zodat de caller automatisch owner wordt.

Hieronder zet ik de patronen op een rij, van meest aanbevolen β†’ meer uitzonderlijk.


βœ… 1. Functie retourneert een Vec<T> (aanbevolen)

Dit is veruit het meest gebruikte en duidelijke patroon.

struct Item {
    id: i32,
    name: String,
}

fn load_items() -> Vec<Item> {
    let mut items = Vec::new();

    items.push(Item { id: 1, name: "A".into() });
    items.push(Item { id: 2, name: "B".into() });

    items
}

Gebruik:

fn main() {
    let items = load_items(); // main is owner
    println!("{}", items.len());
}

Waarom dit goed is

  • Ownership is glashelder
  • Geen lifetimes nodig
  • Compiler kan optimaliseren (NRVO / move elision)
  • Sluit perfect aan bij Rust-idiomen

πŸ‘‰ In 90% van de gevallen is dit wat je wilt


βœ… 2. Result + ownership (bij fouten / IO / services)

Voor services is dit vaak nΓ³g realistischer:

fn load_items() -> Result<Vec<Item>, Box<dyn std::error::Error>> {
    Ok(vec![
        Item { id: 1, name: "A".into() },
        Item { id: 2, name: "B".into() },
    ])
}

Gebruik:

let items = load_items()?; // caller is owner

⚠️ 3. Caller geeft een &mut Vec<T> mee (meestal niet nodig)

Dit kan, maar is alleen zinvol als je expliciet wilt toevoegen aan bestaande data.

fn load_items_into(items: &mut Vec<Item>) {
    items.push(Item { id: 1, name: "A".into() });
}

Gebruik:

let mut items = Vec::new();
load_items_into(&mut items);

Nadelen

  • Minder duidelijk ownership
  • Minder ergonomisch
  • Sneller borrow-complexiteit

πŸ‘‰ Gebruik dit alleen als:

  • je wilt append’en
  • performancekritisch bent
  • of API-compatibiliteit nodig hebt

βœ… 4. Iterator teruggeven (lazy / streaming)

Als je service potentieel veel data heeft of lazy wil zijn:

fn load_items() -> impl Iterator<Item = Item> {
    vec![
        Item { id: 1, name: "A".into() },
        Item { id: 2, name: "B".into() },
    ].into_iter()
}

Gebruik:

let items: Vec<Item> = load_items().collect();

Caller is alsnog owner.


🧠 5. Service-struct met methode (typisch voor β€œservices”)

In jouw β€œservice”-context waarschijnlijk het mooist:

struct ItemService;

impl ItemService {
    fn get_items(&self) -> Vec<Item> {
        vec![
            Item { id: 1, name: "A".into() },
            Item { id: 2, name: "B".into() },
        ]
    }
}

Gebruik:

let service = ItemService;
let items = service.get_items(); // ownership hier

❌ Wat je meestal niet wilt

fn get_items<'a>() -> &'a Vec<Item> { ... }

❌ Probleem:

  • Wie is owner?
  • Lifetime hell
  • Vaak onmogelijk zonder static of interne opslag

🧭 Beslisboom

SituatieBeste keuze
Caller moet owner zijnfn -> Vec<T>
Kan falenfn -> Result<Vec<T>, E>
Grote/lazy datafn -> impl Iterator<Item = T>
Append aan bestaande datafn(&mut Vec<T>)
Service-achtigimpl Service { fn -> Vec<T> }

🎯 Samenvatting

Rust wil dat ownership expliciet is β€” en retourneren is de schoonste vorm daarvan.

βœ”οΈ Laat je functie gewoon een Vec<T> teruggeven
❌ Niet vullen via parameters tenzij je daar echt een reden voor hebt

Als je wilt, kan ik dit ook laten zien met:

  • async services
  • database calls
  • trait-based services
  • Arc<Vec<T>> / shared ownership

Zeg maar πŸ‘

Zeker πŸ‘ Hieronder laat ik drie veelgebruikte manieren zien om deze functie aan te roepen, mΓ©t nette error handling. Ik bouw het voorbeeld stap voor stap op.

Voor de volledigheid eerst een minimale Item:

#[derive(Debug)]
struct Item {
    id: i32,
    name: String,
}

En jouw functie:

fn load_items() -> Result<Vec<Item>, Box<dyn std::error::Error>> {
    Ok(vec![
        Item { id: 1, name: "A".into() },
        Item { id: 2, name: "B".into() },
    ])
}

βœ… 1. Idiomatic: ? gebruiken (meest aanbevolen)

Dit is wat je in bijna alle Rust-services en binaries ziet.

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let items = load_items()?; // fout? β†’ automatisch terug uit main

    for item in items {
        println!("{:?}", item);
    }

    Ok(())
}

Wat gebeurt hier?

  • load_items() geeft Result<Vec<Item>, E>
  • ?:
    • bij Ok β†’ unwrapt de Vec<Item>
    • bij Err β†’ returned direct uit main
  • main mag Result teruggeven β†’ Rust print automatisch de fout

βœ”οΈ Simpel, leesbaar, idiomatisch


βœ… 2. Expliciet matchen (als je zelf wilt reageren)

Handig als je logging of alternatieve flow wilt.

fn main() {
    match load_items() {
        Ok(items) => {
            println!("Items geladen:");
            for item in items {
                println!("{:?}", item);
            }
        }
        Err(err) => {
            eprintln!("❌ Fout bij laden van items: {}", err);
        }
    }
}

βœ”οΈ Volledige controle
❌ Meer boilerplate


βœ… 3. if let Err + early return

Fijn als je snel wilt stoppen:

fn main() {
    let items = match load_items() {
        Ok(items) => items,
        Err(e) => {
            eprintln!("❌ Kan items niet laden: {}", e);
            return;
        }
    };

    println!("{} items geladen", items.len());
}

πŸ§ͺ 4. Met eigen fouttype (iets geavanceerder)

Als je service groter wordt, is dit vaak beter dan Box<dyn Error>:

use std::fmt;

#[derive(Debug)]
enum LoadError {
    DatabaseDown,
}

impl fmt::Display for LoadError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Database is niet bereikbaar")
    }
}

impl std::error::Error for LoadError {}

fn load_items() -> Result<Vec<Item>, LoadError> {
    Err(LoadError::DatabaseDown)
}

Gebruik:

fn main() -> Result<(), LoadError> {
    let items = load_items()?;
    println!("{:?}", items);
    Ok(())
}

🧠 Samenvatting

SituatieAanpak
CLI / servicemain() -> Result<()> + ?
UI / APImatch of map_err
Library codeEigen error type
Snel stoppenmatch + return

πŸ‘‰ Aanrader voor jouw voorbeeld:
Gebruik variant #1 (? in main) β€” schoon en Rust-idiomatisch.

Als je wilt, kan ik dit ook laten zien in:

  • async context (tokio)
  • Web API handler
  • unit tests (assert!(load_items().is_ok()))
  • service trait met meerdere implementaties

Mooi voorbeeld πŸ‘ Dit leent zich perfect om te laten zien hoe je in Rust iteratoren maakt die hetzelfde doen als JS function*.

Rust heeft geen generators zoals JS, maar je bereikt hetzelfde met:

  • structs + Iterator trait
  • of iterator-adapters (map, flat_map)
  • zero-copy waar mogelijk (&str slices)

Ik laat hieronder een bewust eenvoudige CSV-parser zien, vergelijkbaar met jouw JS-code (dus geen volledige RFC4180).


🎯 Doel (1-op-1 met jouw JS)

  • String β†’ regels iterator
  • Regel β†’ CSV-waarden iterator
  • Waarden β†’ decode / trim

1️⃣ Iterator: regels uit een string

In Rust is dit verrassend simpel: lines() is al een iterator.

fn get_lines_from_string(source: &str) -> impl Iterator<Item = &str> {
    source.lines()
}

Gebruik:

for line in get_lines_from_string(csv) {
    println!("line: {}", line);
}

2️⃣ Iterator: CSV-waarden uit één regel

Hier maken we zelf een iterator (vergelijkbaar met function*).

CSV-iterator struct

struct CsvValues<'a> {
    line: &'a str,
    separator: char,
    pos: usize,
    in_string: bool,
}

Constructor

fn get_csv_values_from_line<'a>(
    line: &'a str,
    separator: char,
) -> CsvValues<'a> {
    CsvValues {
        line,
        separator,
        pos: 0,
        in_string: false,
    }
}

Iterator-implementatie

impl<'a> Iterator for CsvValues<'a> {
    type Item = &'a str;

    fn next(&mut self) -> Option<Self::Item> {
        let bytes = self.line.as_bytes();
        let start = self.pos;

        while self.pos < bytes.len() {
            let c = bytes[self.pos] as char;

            if c == '"' {
                self.in_string = !self.in_string;
            } else if !self.in_string && c == self.separator {
                let value = &self.line[start..self.pos];
                self.pos += 1; // separator overslaan
                return Some(value);
            }

            self.pos += 1;
        }

        if start < self.line.len() {
            return Some(&self.line[start..]);
        }

        None
    }
}

βœ”οΈ Dit werkt lazy
βœ”οΈ Geen allocaties
βœ”οΈ Net als JS yield


3️⃣ Waarde decoderen (zoals JS decodeValue)

fn decode_value(value: &str) -> String {
    value
        .trim()
        .trim_matches('"')
        .replace("\"\"", "\"")
}

4️⃣ Alles samen (zoals jouw JS for..of)

fn main() {
    let csv = r#"value1,"value,with,comma","value ""with quotes"" inside",value4"#;

    for line in get_lines_from_string(csv) {
        println!("======================");

        for raw in get_csv_values_from_line(line, ',') {
            let value = decode_value(raw);
            println!("value: {}", value);
        }
    }
}

πŸ–¨οΈ Output

======================
value: value1
value: value,with,comma
value: value "with quotes" inside
value: value4

🧠 Vergelijking JS vs Rust

JSRust
function*struct + impl Iterator
yieldSome(value) in next()
LazyLazy
String copiesZero-copy (&str)
Runtime checksCompile-time safety

πŸš€ Bonus: iterator chaining (erg Rust-achtig)

Je kunt dit ook combineren:

let values: Vec<String> =
    get_lines_from_string(csv)
        .flat_map(|l| get_csv_values_from_line(l, ','))
        .map(decode_value)
        .collect();

πŸ“Œ Wanneer dit patroon gebruiken?

  • streaming parsing
  • grote bestanden
  • zero-copy verwerking
  • pipeline-achtige logica (net als JS generators)

Als je wilt, kan ik ook:

  • async iterators laten zien
  • een CsvParser service maken
  • of laten zien hoe dit eruit ziet met BufRead (file streaming)

Zeg maar πŸ‘

Ja β€” dit is precies het punt waar veel mensen uit JS/Python tegenaan lopen in Rust πŸ™‚
Het korte, eerlijke antwoord is:

❌ Rust heeft geen yield return-syntax zoals JS of C#
βœ… Maar je kunt bijna hetzelfde ergonomische resultaat krijgen met een paar idiomatische patronen.

Ik laat ze oplopend zien, beginnend bij wat het dichtst bij yield komt.


βœ… Optie 1: std::iter::from_fn (het dichtst bij yield return)

Dit is conceptueel bijna 1-op-1 yield.

Definitie zoals jij vroeg

fn get_csv_values_from_line<'a>(
    line: &'a str,
    separator: char,
) -> impl Iterator<Item = &'a str> {
    let mut pos = 0usize;
    let mut in_string = false;

    std::iter::from_fn(move || {
        let bytes = line.as_bytes();
        let start = pos;

        while pos < bytes.len() {
            let c = bytes[pos] as char;

            if c == '"' {
                in_string = !in_string;
            } else if !in_string && c == separator {
                let value = &line[start..pos];
                pos += 1;
                return Some(value); // ⬅️ yield
            }

            pos += 1;
        }

        if start < line.len() {
            pos = line.len();
            return Some(&line[start..]); // ⬅️ final yield
        }

        None
    })
}

Gebruik

for v in get_csv_values_from_line(line, ',') {
    println!("value: {}", v);
}

Waarom dit zo goed is

  • Geen extra struct nodig
  • Geen impl Iterator for ...
  • State zit netjes in de closure
  • Some(...) β‰ˆ yield

πŸ‘‰ Dit is meestal wat je zoekt als je β€œyield return” denkt


🟑 Optie 2: Custom struct + impl Iterator (meest expliciet)

Dit had je al gezien β€” dit is meer boilerplate, maar wel het fundament.

Gebruik dit als:

  • je iterator herbruikbaar is
  • je state wilt exposen
  • je performance-maximalisme wilt

🟑 Optie 3: Vec bouwen en into_iter() (geen echte yield)

fn get_csv_values_from_line<'a>(
    line: &'a str,
    separator: char,
) -> impl Iterator<Item = &'a str> {
    let mut values = Vec::new();
    let mut start = 0;
    let mut in_string = false;

    for (i, c) in line.char_indices() {
        if c == '"' {
            in_string = !in_string;
        } else if !in_string && c == separator {
            values.push(&line[start..i]);
            start = i + 1;
        }
    }

    values.push(&line[start..]);
    values.into_iter()
}

❌ Niet lazy
❌ Extra allocaties
βœ”οΈ Simpel


πŸš€ Optie 4: Nightly Rust (#![feature(generators)]) ❌

Er bestaan generators in nightly Rust, maar:

  • niet stabiel
  • syntax is anders
  • niet aanbevolen voor productie

🧠 Vergelijking met C#/JS

ConceptJS / C#Rust
yieldyield valueSome(value)
Generator stateimplicietclosure / struct
Lazyjaja
Syntax sugarjanee (bewust)
Zero-costβŒβœ…

🎯 Conclusie (belangrijk)

Als je in Rust denkt:
β€œIk wil gewoon een yield-return-achtig ding”
πŸ‘‰ gebruik std::iter::from_fn

Het is:

  • compact
  • leesbaar
  • idiomatisch
  • exact jouw gevraagde signature

Als je wilt, kan ik:

  • async yield-achtige voorbeelden geven
  • laten zien hoe dit eruitziet bij file streaming
  • of dit ombouwen naar een echte CSV-service

Zeg maar πŸ‘

95580cookie-checkRust Mutable Iterator example