0

I have a struct Database { events: Vec<Event> }. I would like to apply some maps and filters to events. What is a good way to do this?

Here's what I tried:

fn update(db: &mut Database) {
    db.events = db.events.into_iter().filter(|e| !e.cancelled).collect();
}

This doesn't work:

cannot move out of `db.events` which is behind a mutable reference
...
move occurs because `db.events` has type `Vec<Event>`, which does not implement the `Copy` trait

Is there any way to persuade Rust compiler that I'm taking the field value only temporarily?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Fixpoint
  • 9,619
  • 17
  • 59
  • 78
  • 1
    [Temporarily move out of borrowed content](https://stackoverflow.com/q/29570781/155423); [How can I swap in a new value for a field in a mutable reference to a structure?](https://stackoverflow.com/q/27098694/155423). – Shepmaster Jul 07 '21 at 17:24

3 Answers3

4

In the case you exposed, the safer way is to use Vec.retain :

fn update(db: &mut Database) {
    db.events.retain(|e| !e.cancelled);
}
Joël Hecht
  • 1,766
  • 1
  • 17
  • 18
4

The conceptual issue of why this doesn't work is due to panics. If, for example, the filter callback panics, then db.events would have been moved out of, by into_iter, but would not have had a value to replace it with - it would be uninitialized, and therefore unsafe.

Joël Hecht has what you really want to do in your specific instance: Vec::retain lets you filter out elements in place, and also reuses the storage.

Alexey Larionov also has an answer involving Vec::drain, which will leave an empty vector until the replacement happens. It requires a new allocation, though.

However, in the general case, the replace_with and take_mut crates offer functions to help accomplish what you are doing. You provide the function a closure that takes the value and returns its replacement, and the crates will run that closure, and aborting the process if there are panics.

Colonel Thirty Two
  • 23,953
  • 8
  • 45
  • 85
2

Alternatively to @Joël Hecht's answer, you can Vec::drain the elements to then recreate the vector. Playground

fn update(db: &mut Database) {
    db.events = db.events
        .drain(..)
        .filter(|e| !e.cancelled)
        .collect();
}
Alexey S. Larionov
  • 6,555
  • 1
  • 18
  • 37