fadedbee
fadedbee

Reputation: 44807

Cannot return reference to local variable

I have a trait:

pub trait HasQuux {
    fn quux(&self) -> &Quux;
}

Most structs which implement this have a Quux field, possibly nested, to which they can return a reference. e.g.

pub struct Foo {
    ...
    q: Quux,
    ...
}

impl HasQuux for Foo {
    fn quux(&self) -> &Quux {
        &self.q
    }
}

How can I implement HasQuux for structs which must calculate their Quux?

This implementation:

impl HasQuux for Bar {
    fn quux(&self) -> &Quux {
        let q: Quux = self.calc_quux();
        &q
    }
}

causes:

error[E0515]: cannot return reference to local variable `q`
  --> /home/fadedbee/test.rs:38:3
   |
38 |         &q
   |         ^^ returns a reference to data owned by the current function

I am keen for quux() to return a reference, as 99% of structs implementing HasQuux have a Quux field.

I understand why this isn't possible.

Can I create a temporary Quux whose lifetime matches the lifetime of Bar and return a reference to that?

Or is there a better solution?

Upvotes: 1

Views: 1510

Answers (1)

user4815162342
user4815162342

Reputation: 155495

Can I create a temporary Quux whose lifetime matches the lifetime of Bar and return a reference to that?

The short answer is: you can't. What you can do is have quux return a Cow (copy-on-write, not the bovine):

fn quux(&self) -> Cow<'_, Quux>

The 99% of impls that have a self.q will return Cow::Borrowed(&self.q), and the 1% that don't will return Cow::Owned(self.calc_quux()). (Playground.)

If you can't change the trait definition, then it is very unlikely that you will manage to return a reference in safe Rust (without leaking Quux). First, you'd need to store the calculated q in an Option<Quux> inside self, which is probably not what you want. But even if it were, you'd have the issue of HasQuux::quux taking &self and not &mut self. Using RefCell<Option<Quux>> would allow you to store the calculated Quux, but not to return a reference to it because Rust can't prevent you from overwriting the RefCell contents elsewhere in the code.

If it is acceptable to cache the &Quux given out, you can implement a helper type1 to encapsulate interior mutability:

mod lazy {
    use std::cell::RefCell;

    #[derive(Debug, Default)]
    pub struct Lazy<T> {
        content: RefCell<Option<T>>,
    }

    impl<T> Lazy<T> {
        pub fn ensure(&self, make_content: impl FnOnce() -> T) -> &T {
            if self.content.borrow().is_none() {
                *self.content.borrow_mut() = Some(make_content());
            }
            // safety: self.content.borrow_mut() must never be called again
            // because we give out a shared reference to the content
            let content = unsafe { &*self.content.as_ptr() };
            // unwrap: we've ensured above that the option is Some
            content.as_ref().unwrap()
        }
    }
}

The above type uses unsafe in its implementation, but provides a fully safe interface (as far as I can tell - one can never be 100% sure with unsafe code). Another borrow_mut() mentioned in the safety comment cannot be executed by outside code because the fields of Lazy are private. Still, if the definition of Lazy were modified incorrectly, the compiler couldn't catch the error because we used unsafe to convince it that we know what we're doing.

With this helper type, we can provide a simple and safe implementation of HasQuux for Bar:

struct Bar {
    cached_q: lazy::Lazy<Quux>,
}

impl Bar {
    fn calc_quux(&self) -> Quux {
        Quux
    }
}

impl HasQuux for Bar {
    fn quux(&self) -> &Quux {
        self.cached_q.ensure(|| self.calc_quux())
    }
}

Playground


1

Or just use the OnceCell type from the once_cell crate whose get_or_init method is exactly equivalent to Lazy::ensure() defined above.

Upvotes: 5

Related Questions