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
  • 326,504
  • 69
  • 892
  • 1,159
Fixpoint
  • 9,321
  • 17
  • 58
  • 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,534
  • 1
  • 15
  • 16
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
  • 20,587
  • 7
  • 38
  • 76
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 Larionov
  • 5,149
  • 1
  • 16
  • 32