Reputation: 47988
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
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