Reputation: 44807
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
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())
}
}
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