Is there a more erganomic way to create &mut T where `Option<&mut T>` is None?

I need to initiate an &mut T where Option<&mut T> returns None. I'm quite a fan of single lines like unwrap_or_else and such methods, and wish to do something like the following:

fn maybe_takes_mutable_reference(value: Option<&mut Vec<usize>>) {
    takes_vec(value.unwrap_or_else(|| &mut recreate_expensive_vec()))
}

fn takes_vec(vec: &mut Vec<usize>) {
    todo!()
}

fn recreate_expensive_vec() -> Vec<usize> {
    todo!()
}

However it doesn't work of course, because the closure creates then drops the Vec. So I need to do something like the following:

fn maybe_takes_mutable_reference_two(optional_value: Option<&mut Vec<usize>>) {
    if let Some(passed_vec) = optional_value {
        takes_vec(passed_vec)
    } else {
        let mut new_vec = recreate_expensive_vec();
        takes_vec(&mut new_vec);
    }
}

Unfortuantly things get a little more complicated when I have a few more optional mutable references coming in, and have to match on all of them and do new allocations only when necessary. Is there a more ergonomic way to create an &mut T in less lines (without macros)?

Your second example can be simplified to this (same idea, i.e. have a vector in the scope of maybe_takes_mutable_reference_two, rather than creating it inside the closure of unwrap_or_else):

fn maybe_takes_mutable_reference(value: Option<&mut Vec<usize>>) {
    let mut v = Vec::new();
    takes_vec(value.unwrap_or_else(|| &mut v))
}

fn takes_vec(vec: &mut Vec<usize>) {
    todo!()
}

Playground.

2 Likes

Vec::new() doesn't allocate.

3 Likes

Even shorter:

takes_vec(value.unwrap_or(&mut Vec::new()));
4 Likes

Thanks for the responses. Just to clarify, The Vec was an example. In reality the &mut T is quite a larged collection with the proper capacity and values that need to be recreated if it isn't passed in. So for the example given, we can assume that avoiding recreating the Vec is expensive. I've updated the example to reflect this.

1 Like

If you have a T that does need significant work to initialize and you find yourself needing this pattern a lot, you can define a helper type to defer the initialization:

enum Lazy<T, F> {
    Here(T),
    Uninit(F),
    Poisoned,
}

impl<T, F: FnOnce() -> T> Lazy<T, F> {
    fn new(f: F) -> Self {
        Lazy::Uninit(f)
    }
    fn get_mut(&mut self) -> &mut T {
        if let Lazy::Here(x) = self {
            return x;
        }
        let Lazy::Uninit(f) = std::mem::replace(self, Lazy::Poisoned) else {
            unreachable!()
        };
        *self = Lazy::Here(f());
        self.get_mut()
    }
}

// —————

fn maybe_takes_mutable_reference(value: Option<&mut Vec<usize>>) {
    let mut temp_vec = Lazy::new(Vec::new);
    takes_vec(value.unwrap_or_else(|| temp_vec.get_mut()))
}

fn takes_vec(vec: &mut Vec<usize>) {
    todo!()
}

(Note: This is very similar to LazyCell without the interior mutability or OnceCell::get_mut_or_init(), which are both currently unstable)

1 Like

Since the relevant methods on LazyCell/OnceCell are still unstable you could just use Option::insert/Option::get_or_insert_with as an alternative:

fn maybe_takes_mutable_reference(value: Option<&mut Vec<usize>>) {
    let mut fallback = None;
    takes_vec(value.unwrap_or_else(|| fallback.insert(recreate_expensive_vec())))
}

fn takes_vec(vec: &mut Vec<usize>) {
    todo!()
}

fn recreate_expensive_vec() -> Vec<usize> {
    todo!()
}
5 Likes

This version calls recreate_expensive_vec unconditionally, even when it is not needed.

(The get_or_insert_with does not help because fallback is always initialized to None.)

1 Like

Oh right, I tried being fancy and got bitten, I'll edit it out. The first version should be fine though.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.