Writing a Rust function modifying a struct member in elements of a generic container

39 Views Asked by At

Is it possible to write a function which modifies a struct member in place such that one can use it with both Vec and HashMap::values() ?

This is what I came up so far:

use std::collections::HashMap;

struct Foo {
    x: i32,
}

fn process_collection<T>(collection: &mut T)
where
    T: IntoIterator<Item = Foo>,
    for<'a> &'a mut T: IntoIterator<Item = &'a mut Foo>,
{
    for item in collection.into_iter() {
        item.x = 5;
    }
}

fn main() {
    let mut numbers = vec![Foo { x: 1 }, Foo { x: 2 }];
    // Set all .x members to 5.
    process_collection(&mut numbers); 
    
    for item in &numbers {
        println!("item after: {}", item.x);
    }
    
    let mut data: HashMap<String, Foo> = HashMap::new();
    data.insert("One".to_string(), Foo {x: 1} );
    data.insert("Two".to_string(), Foo {x: 2});
    
    // This does not compile.
    process_collection(&mut data.values()); 
}
2

There are 2 best solutions below

0
cafce25 On

values iterates over immutable references to the values, you have to use values_mut if you need exclusive references to be able to mutate the values.

Additionally writing out the mutable reference in &mut T is extremely limiting and does not allow ValuesMut (an iterator over mutable references to the values) at all (neither does it implement IntoIterator, instead just take T: IntoIterator<&mut Foo>:

fn process_collection<'a, T>(collection: T)
where
    T: IntoIterator<Item = &'a mut Foo>,
{
    for item in collection.into_iter() {
        item.x = 5;
    }
}

fn main() {
    let mut numbers = vec![Foo { x: 1 }, Foo { x: 2 }];
    // Set all .x members to 5.
    process_collection(&mut numbers); 
    
    for item in &numbers {
        println!("item after: {}", item.x);
    }
    
    let mut data: HashMap<String, Foo> = HashMap::new();
    data.insert("One".to_string(), Foo {x: 1} );
    data.insert("Two".to_string(), Foo {x: 2});
    
    process_collection(data.values_mut()); 
    for item in data.values() {
        println!("item after: {}", item.x);
    }
}
0
kmdreko On

To modify the elements in place, you only need an iterator with mutable access:

fn process_collection<'a, T>(collection: T)
where
    T: IntoIterator<Item = &'a mut Foo>,
{
    for item in collection {
        item.x = 5;
    }
}

With which you can call with a Vec<Foo> like this:

process_collection(&mut numbers);

Or with the values from a HashMap<_, Foo> like this:

process_collection(data.values_mut()); 

Note the changes:

  • You do not need both T: IntoIterator and &mut T: IntoIterator. Though collections often have both implementations, that is not needed and wouldn't work for the values of a HashMap.
  • This uses values_mut() instead of just values() since the latter only provides immutable references.
  • collection is passed as an owned value and not as a mutable reference since it is more idiomatic.