Search code examples
rustreferenceclosuresborrow-checker

How do you get around "cannot return value referencing temporary value" generated in a closure?


I have a struct, Thing that implements Clone but not Copy, and it has two methods, one for doing work, and one for deriving a "child" Thing from an existing "parent" Thing. Not every Thing has a child, so sometimes child derivation returns None.

Here is the API:

#[derive(Clone)]
struct Thing;

impl Thing {
    fn do_work(&self) {}
    
    // This is considered expensive
    fn try_get_child(&self) -> Option<Thing> {
        Some(Thing)
    }
}

I would like to implement the function described below:

/// If `b` is Some then do work on the Thing inside
/// Otherwise, try to get a child from `a` and do work on that child
/// If both `b` and `a.try_get_child()` are None, do nothing.
fn f(a: Thing, b: Option<&Thing>) {
    b.or_else(|| a.try_get_child().as_ref()).map(Thing::do_work);
}

But this implementation causes a compile error:

cannot return value referencing temporary value

I think I understand that this error is raised because neither closures nor functions can return references to values created inside their bodies because such values are dropped when execution returns from the function or closure. But what is the idiomatic way to implement fn f such that the child derivation is only done if necessary? Is it possible using the methods of Option?


Solution

  • Using Option's helper methods, if you are willing to duplicate the code (you can extract it into functions of course):

    fn f(a: Thing, b: Option<&Thing>) {
        b.map(Thing::do_work).or_else(|| a.try_get_child().as_ref().map(Thing::do_work));
    }
    

    Without duplicating the code (or if the return value of do_work() borrows from the Thing), using a neat trick to use a maybe-initialized variable:

    fn f(a: Thing, b: Option<&Thing>) {
        let a_child;
        let value = match b {
            Some(_) => b,
            None => {
                a_child = a.try_get_child();
                a_child.as_ref()
            }
        };
        value.map(Thing::do_work);
    }