How to pass `Option<&mut ...>` to multiple function calls without causing move errors?
Asked Answered
P

3

2

Since it's possible to pass a mutable reference to a vector around (without causing moves), how can an Option<reference> be passed to functions multiple times without causing borrow checking errors?

This simple example just shows what happens when an Option<&mut Vec<usize>> is passed multiple times to a function:

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

    return c;
}

fn maybe_push_multi(v_option: Option<&mut Vec<usize>>) -> usize {
    let mut c = 0;
    c += maybe_push(v_option);
    c += maybe_push(v_option);
    c += maybe_push(None);
    return c;
}

fn main() {
    let mut v: Vec<usize> = vec![];
    let v_option = Some(&mut v);
    println!("{}", maybe_push_multi(v_option));
}

(Playground)

Gives the error:

error[E0382]: use of moved value: `v_option`
  --> <anon>:17:21
   |
16 |     c += maybe_push(v_option);
   |                     -------- value moved here
17 |     c += maybe_push(v_option);
   |                     ^^^^^^^^ value used here after move
   |
   = note: move occurs because `v_option` has type `std::option::Option<&mut std::vec::Vec<usize>>`, which does not implement the `Copy` trait
Popsicle answered 17/11, 2016 at 15:13 Comment(0)
W
5

You can pass the Option by reference too, if you don't want it moved into the function.

fn maybe_push(mut v_option: &mut Option<&mut Vec<usize>>) -> usize

// ...

maybe_push_twice(&mut v_option);

Then replace:

maybe_push(None);

With:

maybe_push(&mut None);
Whydah answered 17/11, 2016 at 15:18 Comment(2)
This is how I resolved it, but I admit it looks REALLY UGLY.Adherence
Update: The answer from @maoe using .as_deref_mut() is the solution.Adherence
C
2

You can destructure the Option with a match expression and then create a new Option value for every call of the function maybe_push():

fn maybe_push_twice(v_option: Option<&mut Vec<usize>>) -> usize {
    let mut c = 0;
    match v_option {
        Some(v) => {
            c += maybe_push(Some(v));
            c += maybe_push(Some(v));
        }
        None => {
            c += maybe_push(None);
            c += maybe_push(None);        
        }
    };
    return c;
}

Here is a more convenient way:

fn maybe_push_twice(mut v_option: Option<&mut Vec<usize>>) -> usize {
    let mut c = 0;
    c += maybe_push(v_option.as_mut().map(|x| &mut **x));
    c += maybe_push(v_option);
    return c;
}

You can use a trait instead of a macro:

trait RefMut<T> {
    fn ref_mut(&mut self) -> Option<&mut T>;
}

impl<'t, T> RefMut<T> for Option<&'t mut T>{
    #[inline]
    fn ref_mut(&mut self) -> Option<&mut T>{
        self.as_mut().map(|x| &mut**x)
    }
}

fn maybe_push_twice(mut v_option: Option<&mut Vec<usize>>) -> usize {
    let mut c = 0;
    c += maybe_push(v_option.ref_mut());
    c += maybe_push(v_option);
    return c;
}
Cytology answered 18/11, 2016 at 18:34 Comment(1)
Good to know as an alternative to the existing answer. While a valid, this makes it annoying to use when passing an argument to functions many times. I'd be tempted to put opt.as_mut().map(|x| &mut **x) into a macro.Popsicle
A
2

As of 1.40.0, you can use Option::as_deref_mut.

fn maybe_push_twice(mut v_option: Option<&mut Vec<usize>>) -> usize {
    let mut c = 0;
    c += maybe_push(v_option.as_deref_mut());
    c += maybe_push(v_option);
    c
}

Here is a playground modified from the original playground.

Acidulate answered 25/7, 2022 at 6:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.