Clean way to get Option::unwrap_or_else behaviour with an Option<&T>

Viewed 121

I would like to know if there's any elegant solution for getting code/behaviour similar to unwrap_or_else on an Option<&T>. My use case is to pass an optional reference to a function and if it's not used then create a default value of the same type to use. Here's a boiled-down version of my code:

#[derive(Debug)]
struct ExpensiveUnclonableThing {}

fn make_the_thing() -> ExpensiveUnclonableThing {
    // making the thing is slow
    // ...
    ExpensiveUnclonableThing {}
}

fn use_the_thing(thing_ref: &ExpensiveUnclonableThing) {
    dbg!(thing_ref);
}

fn use_or_default(thing_ref_opt: Option<&ExpensiveUnclonableThing>) {
    enum MaybeDefaultedRef<'a> {
        Passed(&'a ExpensiveUnclonableThing),
        Defaulted(ExpensiveUnclonableThing),
    }
    let thing_md = match thing_ref_opt {
        Some(thing_ref) => MaybeDefaultedRef::Passed(thing_ref),
        None => MaybeDefaultedRef::Defaulted(make_the_thing()),
    };
    let thing_ref = match &thing_md {
        MaybeDefaultedRef::Passed(thing) => thing,
        MaybeDefaultedRef::Defaulted(thing) => thing,
    };
    use_the_thing(thing_ref);
}

fn use_or_default_nicer(thing_ref_opt: Option<&ExpensiveUnclonableThing>) {
    let thing_ref = thing_ref_opt.unwrap_or_else(|| &make_the_thing());
    use_the_thing(thing_ref);
}

fn main() {
    let thing = make_the_thing();

    use_or_default(Some(&thing));
    use_or_default(None);

    use_or_default_nicer(Some(&thing));
    use_or_default_nicer(None);
}

The thing is dropped right away when the unwrap_or_else closure ends, so I of course get an error stating that I can't do that:

error[E0515]: cannot return reference to temporary value
  --> src/main.rs:31:53
   |
31 |     let thing_ref = thing_ref_opt.unwrap_or_else(|| &make_the_thing());
   |                                                     ^----------------
   |                                                     ||
   |                                                     |temporary value created here
   |                                                     returns a reference to data owned by the current function

What is the 'idiomatic Rust' way of writing use_or_default? Is there a way I can get it to look similar to how use_or_default_nicer is implemented other than by creating a generic MaybeDefaultedRef<T> type + with some convenience methods? I am open to refactoring the whole thing if there's a better way.

2 Answers

You can write something like this:

fn use_or_default_nicer(thing_ref_opt: Option<&ExpensiveUnclonableThing>) {
    let mut maybe = None;
    let thing_ref = thing_ref_opt.unwrap_or_else(
        || maybe.insert(make_the_thing())
    );
    use_the_thing(thing_ref);
}

That is, you can keep the value itself outside of the function and then assign to it if necessary. Unfortunately, an unitialized value cannot be capture by a lambda so you have to make the variable Option<ExpensiveUnclonableThing> and initialize with None.

But in a real code of mine, I had the same issue and I wrote a manual match:

fn use_or_default_nicer(thing_ref_opt: Option<&ExpensiveUnclonableThing>) {
    let maybe;
    let thing_ref = match thing_ref_opt {
        Some(x) => x,
        None => {
            maybe = make_the_thing();
            &maybe
        }
    };
    use_the_thing(thing_ref);
}

In my opinion this is nicer even if a bit longer, because you don't need the Option<_> or the maybe variable being mutable` or the fake initialization.

Some people feel a bit of a defeat when they match on an Option, and think it is un-idiomatic, but I don't particularly care.

A plain old if/else would do also, no need to convolute things:

fn use_or_default_nicer(thing_ref_opt: Option<&ExpensiveUnclonableThing>) {
    if let Some(e) = thing_ref_opt {
        use_the_thing(e);
    } else {
        let e = make_the_thing();
        use_the_thing(&e);
    }
}

Playground

Related