ideasman42
ideasman42

Reputation: 47988

How to access a vector multiple times within an 'Option'?

How to access a vector within an option without Rust moving it on the first access?

fn maybe_push(v_option: Option<&mut Vec<usize>>) -> usize {
    let mut c = 0;
    if let Some(v) = v_option {
        for i in 0..10 {
            v.push(i);
            c += i;
        }
    } else {
        for i in 0..10 {
            c += i;
        }
    }

    // second access, fails
    if let Some(v) = v_option {
        for i in 10..20 {
            v.push(i);
            c += i;
        }
    } else {
        for i in 10..20 {
            c += i;
        }
    }

    return c;
}


fn main() {
    let mut v: Vec<usize> = vec![];

    println!("{}", maybe_push(Some(&mut v)));
    println!("{}", maybe_push(None));

    println!("{:?}", v);
}

This gives the error:

error[E0382]: use of partially moved value: `v_option`
  --> src/main.rs:16:22
   |
4  |     if let Some(v) = v_option {
   |                 - value moved here
...
16 |     if let Some(v) = v_option {
   |                      ^^^^^^^^ value used here after move

Using if let Some(ref mut v) = v_option { which has been suggested fails too:

error: cannot borrow immutable anonymous field `(v_option:std::prelude::v1::Some).0` as mutable
 --> src/main.rs:4:21
  |
4 |         if let Some(ref mut v) = v_option {
  |                     ^^^^^^^^^

error: cannot borrow immutable anonymous field `(v_option:std::prelude::v1::Some).0` as mutable
  --> src/main.rs:17:21
   |
17 |         if let Some(ref mut v) = v_option {
   |                     ^^^^^^^^^

Upvotes: 2

Views: 1647

Answers (1)

user4815162342
user4815162342

Reputation: 154886

Matching by value moves the value into the pattern variables. Moving makes the original value unusable, except for the very simple objects that implement the Copy trait, such as numbers. Unlike pointers in C, mutable references are not copyable, which can be seen in the following example that doesn't compile either:

let mut v = vec![1, 2, 3];
let rv = &mut v;  // mutable reference to v
{
    // move rv to r1, r1 now becomes the sole mutable reference to v
    let r1 = rv;
    r1.push(4);
}
{
    let r2 = rv;  // error: rv was already moved to r1
    r2.push(5);
}

Rust rejects the above because it enforces the general rule prohibiting multiple mutable references to an object. Despite this particular snippet being safe, allowing multiple mutable references to the same object at the same time would make it easy to write the kind of unsafe programs that Rust is explicitly designed to prevent, e.g. those that contain data races in multithreaded code, or those that access data through an invalidated iterator. Thus the assignment let r1 = rv can only move the rv reference to r1, disallowing the let r2 = rv statement which now refers to moved variable rv.

To fix the code, we must create separate references to the reference. This will create two distinct mutable references to the original reference rather than moving the original mutable reference into an inner scope:

let mut v = vec![1, 2, 3];
let mut rv = &mut v;
{
    // rr1 is a *new* mutable reference to rv - no move is performed
    let rr1 = &mut rv;
    rr1.push(4);
}
{
    // rr2 is a *separate* new mutable reference to rv - also no move
    let rr2 = &mut rv;
    rr2.push(5);
}

The syntax of push invocation is the same with r1.push and rr1.push because Rust's . operator will automatically dereference any number of references.

To return to the example from the question, the reference to Vec<usize> in the Option is like the v reference above, and matching it using the Some(v) pattern moves the reference into the v pattern variable.

To fix it, the pattern must be changed as in the above example, to specify a variable that refers to the value which is itself a reference. This is achieved using the if let Some(ref mut v) syntax. As above, where the rv declaration had to be changed to mutable, this fix also requires the Option to be mutable. With those two changes, the code compiles:

fn maybe_push(mut v_option: Option<&mut Vec<usize>>) -> usize {
    let mut c = 0;
    if let Some(ref mut v) = v_option {
        for i in 0..10 {
            v.push(i);
            c += i;
        }
    } else {
        for i in 0..10 {
            c += i;
        }
    }

    if let Some(ref mut v) = v_option {
        for i in 10..20 {
            v.push(i);
            c += i;
        }
    } else {
        for i in 10..20 {
            c += i;
        }
    }

    return c;
}

fn main() {
    let mut v: Vec<usize> = vec![];

    println!("{}", maybe_push(Some(&mut v)));
    println!("{}", maybe_push(None));

    println!("{:?}", v);
}

Another possibility is to use the as_mut() method to return the option content as a mutable reference to the previous content. This is conceptually equivalent to the change from let r1 = rv to let rr1 = &mut rv in the first snippet, and would allow the use of if let Some(v) pattern, where v would still be a reference to mutable reference to the vector. This also requires that v_option is declared as mutable.

Upvotes: 8

Related Questions