Avoiding Option::unwrap after Option::replace

I have some code of the form

if let Some(x) = some_option.replace(X::new()) {
  // do some stuff
}
some_option
  .as_mut()
  .map(|new_x| new_x.something())
  .expect("won't fail because of the replace call above")

I'm trying to avoid the expect, which seems like it should be doable. But I can't figure out how.
I can replace the .replace() call with .take(), and then set the option later, but that still leaves me with lifetime issues since setting some_option = Some(new_x) requires moving new_x, so I can't then return new_x.something() which requires a mutable borrow of new_x.

Is there some way to chain the setting of an option that returns a reference to the new value?

This is by no means a perfect solution, but: Option::get_or_insert_with is really useful in situations like this.

The problem with get_or_insert_with in your situation is: you can't easily tell, from the outside, whether it is get-ing or insert-ing. Here's a version that uses a mutable local variable to track that; I consider it somewhat ugly because it mentions the new value twice.

    let mut inserted = false;
    let option_ref = some_option.get_or_insert_with(|| {
        inserted = true;
        X::new()
    });
    if !inserted { *option_ref = X::new() }
    option_ref.something()

get_or_insert_with works perfectly, and I don't even need to distinguish get vs. insert since a previous take call will guarantee insert:

if let Some(x) = some_option.take() {
  // do some stuff
}
some_option.get_or_insert_with(X::new).something()

I missed it in the docs because I only searched for "-> T" instead of "-> &mut T". Now I know to search for more variants :slight_smile:

While I do agree such unwrap/expect is ugly, I'm pretty sure that unwrap-to-panic code will be optimized out in your first example. You've written some_option's enum tag as Some, and compiler can see that nobody changed it since unwrap, so it's trivial for compilers that panic-arm is dead code.

get_or_insert[_with] is a std-featured solution that almost gets the job done, but I too have been in this situation, and I really wish there was a

fn put (self: &'_ mut Option<T>, value: T>) -> &'_ mut T

in std.

1 Like

Your put looks like Option::get_or_insert.

Not exactly, get_or_insert either gets or inserts :sweat_smile: I am talking about directly getting the insert part:

    pub fn get_or_insert(&mut self, v: T) -> &mut T {
        match *self {
            None => *self = Some(v),
            _ => (),
        }

        match *self {
            Some(ref mut v) => v,
            None => unsafe { hint::unreachable_unchecked() },
        }
    }

could become

    pub
    fn get_or_insert (self: &'_ mut Self, v: T) -> &'_ mut T
    {
        match *self {
            | Some(ref mut inner) => inner,
            | None => self.put(v),
        }
    }

And yes, put could be renamed to insert

1 Like
pub fn put(&mut self: v: T) -> &mut T {
    *self = None;
    self.get_or_insert(v)
}

And put can be implemented in terms of get_or_insert (although it will give the optimizer a harder time)

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.