Can you lookup an element of a vector and change it?

Advertisements

Assuming I have those structs (no fancy pointer, everything is copyable)

#[derive(Debug, Copy, Clone)]
struct Item {
    id: u32,
    pub value: u32
}

I can create a struct to hold a vec of such items:

#[derive(Debug)]
struct Container {
    next_id: u32,
    items: Vec<Item>
}

It’s fairly easy to add elements, and change them if you know the exact position:

impl Container {
    fn new() -> Container {
        Container {
            next_id: 0,
            items: vec!()
        }
    }

    fn add(&mut self) -> u32 {
        let id = self.next_id;
        self.items.push(Item {
            id: self.next_id,
            value: 0
        });
        self.next_id = self.next_id + 1;
        id
    }

    fn lookup(&mut self, id: u32) -> Option<&Item> {
        self.items.iter().find(|item| item.id == id)
    }

    fn change_first(&mut self, new_value: u32) {
        if self.items.len() < 0 {
            panic!("No item to change");
        }
        self.items[0].value = new_value;
    }
}

This makes such a test pass:

    #[test]
    fn can_change_first() {
        let mut c = Container::new();
        assert_eq!(c.add(), 0);
        c.change_first(22);
        assert_eq!(c.lookup(0).unwrap().value, 22);
    }

However, suppose I want to lookup and element and find it. I don’t want to expose the
items vec directly, but propose an API to do so:

 #[test]
    fn can_lookup_and_change() {
        let mut c = Container::new();
        assert_eq!(c.add(), 0);
        c.change_at(0, 22);
        assert_eq!(c.lookup(0).unwrap().value, 22);
    }

The naïve implementation of change_at would be:

    fn change_at(&mut self, id: u32, new_value: u32) {
        let lookup = self.lookup(id);
        match lookup {
            Some(item) => {
                item.value = new_value;
            }
            None => {
                // ...
            }
        }
    }

However, the compiler does not accept that, giving the error:

   Compiling web v0.1.0 (/home/phtrivier/perso/prj/wirth/dom-web)
error[E0594]: cannot assign to `item.value`, which is behind a `&` reference
   --> redacted/src/lib.rs:170:17
    |
169 |             Some(item) => {
    |                  ---- consider changing this binding's type to be: `&mut Item`
170 |                 item.value = new_value;
    |                 ^^^^^^^^^^^^^^^^^^^^^^ `item` is a `&` reference, so the data it refers to cannot be written

For more information about this error, try `rustc --explain E0594`.

I don’t understand if the error is on the lookup side (is there a different way to lookup up items that will make them mut ?) or on the change_at side (is this the right way to pattern match ?)

>Solution :

You can add a lookup_mut method. This would replace iter with iter_mut.

fn lookup_mut(&mut self, id: u32) -> Option<&mut Item> {
    self.items.iter_mut().find(|item| item.id == id)
}

And then use that when you need a mutable reference.

let lookup = self.lookup_mut(id);

Leave a ReplyCancel reply