Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extracting a mutable reference from an Option

Tags:

rust

Is there any option to extract a mutable reference out of an Option<&mut Foo>? All I found was as_ref() which extracts an immutable reference.

like image 596
fptech20 Avatar asked Apr 21 '26 13:04

fptech20


2 Answers

Is there any option to extract a mutable reference outside of a Option<&mut Foo>.

Yes, but it requires a bit of effort to prevent consuming the Option, since &mut T is not Copy.

Let's assume you have an Option<&mut Foo> which is Some. (The same applies for a non-Some option, just replace unwrap() with appropriate matching.) For example:

let mut foo = Foo;
let mut opt = Some(&mut foo);

opt.unwrap() will give you &mut Foo, but it will move it out of the Option, because &mut Foo is not Copy:

let mut_ref = opt.unwrap();
drop(mut_ref);          // done with mut_ref
println!("{:?}", opt);  // XXX doesn't compile, opt is consumed

That will leave the Option unusable even after mut_ref is out of scope. We don't want that, we want to borrow the inside of the option and retain the use of the Option once that borrow is dropped. For that we can use Option::as_mut:

let mut_ref_ref = opt.as_mut().unwrap();
drop(mut_ref_ref);
println!("{:?}", opt);  // compiles

There are two things to note here: first, you need to use as_mut(), not as_ref() as you attempted, because as_ref() would give you a mutable reference behind a shared reference, which renders it useless. Second, unwrapping as_mut() returns an &mut &mut Foo, not the &mut Foo we wanted. It's very close, though - for example, auto-dereferencing allows you to call a function that accepts &mut Foo:

fn wants_ref(_r: &mut Foo) {}
let mut_ref_ref = opt.as_mut().unwrap();  // got &mut &mut Foo
wants_ref(mut_ref_ref);  // but we can send it to fn expecting &mut Foo

If you for some reason wanted an actual &mut Foo, one would think you'd get it by dereferencing the &mut &mut Foo. However, this doesn't work:

// XXX doesn't compile
let mut_ref = *opt.as_mut().unwrap();

That doesn't compile because * tries to dereference &mut &mut Foo and extract the underlying &mut Foo. But &mut Foo is not Copy, so that's attempting to "move out of a mutable reference", which rustc will tell you cannot be done. However, in this case it can, you need to perform an explicit reborrow, i.e. turn EXPR to &mut *EXPR, where EXPR evaluates to a mutable reference. &mut *EXPR will create a new mutable reference to the same data. It doesn't count as aliasing because the old mutable reference will be (statically unusable) until the new one is dropped. It's basically the same mechanism that allows projection to work on mutable references (i.e. let x_ref = &mut point.x, with point being an &mut Point) - but applied to the object itself.

With explicit reborrow it looks like this:

// `&mut *` is reborrow, and `*` dereferences `&mut &mut Foo`
let mut_ref = &mut **opt.as_mut().unwrap();
wants_ref(mut_ref);
println!("{:?}", opt);  // opt is still usable

Playground

like image 115
user4815162342 Avatar answered Apr 23 '26 09:04

user4815162342


The correct way of doing that is through the as_mut() method.

One tricky bit is that you need to satisfy the &mut self parameter type. The key observation is that if you own a Option<&mut Foo> value, you can safely turn into a mut version of the same (as you are the only owner of it). That is, you can freely go from a Option<&mut Foo> (owned, immutable) to a mut Option<&mut Foo> (owned, immutable) and call the .as_mut() on that to reach the &mut Foo (reference, mutable).

So overall it looks like this:

let maybe_foo: Option<&mut Foo> = Some(&mut foo);

let mut maybe_foo = maybe_foo;
if let Some(mutable_reference_to_foo) = maybe_foo.as_mut() {
    ...
}

Playground

like image 22
kaeso Avatar answered Apr 23 '26 10:04

kaeso



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!