Rust setting vector value within match


#1

I have the following code snippet:

enum Foo {
    Empty,
    Bar {s: String},
}

impl Foo {
    pub fn del(v: &mut Vec<Foo>, st: String) {
        let i = 0;
        match v[i] {
            Foo::Empty => (),
            Foo::Bar {ref s} => {
                if *s == st {
                    v[i] = Foo::Empty;
                }
            }
        }
    }
}

It fails to compile due to:

rustc 1.17.0 (56124baa9 2017-04-24)
error[E0502]: cannot borrow `*v` as mutable because it is also borrowed as immutable
  --> <anon>:13:21
   |
9  |         match v[i] {
   |               - immutable borrow occurs here
...
13 |                     v[i] = Foo::Empty;
   |                     ^ mutable borrow occurs here
...
16 |         }
   |         - immutable borrow ends here

error: aborting due to previous error

However, the following snippet compiles:

enum Foo {
    Empty,
    Bar {s: String},
}

impl Foo {
    pub fn del(v: &mut Vec<Foo>, st: String) {
        let i = 0;
        match v[i] {
            Foo::Empty => (),
            Foo::Bar {..} => {
                v[i] = Foo::Empty;
            }
        }
    }
}

I imagine the latter compiles because I am not referencing s from within v[i]. However, what I am doing in the first snippet is safe given that the reference to s is not used after I have updated the value of v[i]. Is there a syntax I could use to get the first snippet to pass compilation?


#2

The issue is that Rust currently doesn’t support non-lexical lifetimes, and thinks that v is still borrowed, even if nothing that is borrowed from it (s, to be exact) will be used anymore, and can be dropped.

A workaround that moves the assignment out of a ref s scope could look like this:

enum Foo {
    Empty,
    Bar {s: String},
}

impl Foo {
    pub fn del(v: &mut Vec<Foo>, st: String) {
        let i = 0;
        let replace = match v[i] {
            Foo::Empty => false,
            Foo::Bar {ref s} => *s == st,
        };
        if replace {
            v[i] = Foo::Empty;
        }
    }
}

Checking the assembly, Rust compiler can optimize that to not explicitly create a boolean variable.

(you may also want to accept v: &mut [Foo], st: &str, as your function doesn’t need a vector (it doesn’t push to it) and doesn’t need to own a string, and then just say s == st)


#3

if foo implements PartialEq you could do this

        match v[i] {
            Foo::Empty => (),
            ref mut vi @ Foo::Bar {..} if *vi == Foo::Bar{s: st} => {
                    *vi = Foo::Empty;
            }
            Foo::Bar{..} => ()
        }