不能 return 引用局部变量

Cannot return reference to local variable

我有一个特点:

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

大多数实现此功能的结构都有一个 Quux 字段,可能是嵌套的,它们可以 return 对其进行引用。例如

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

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

如何为必须计算其 Quux 的结构实现 HasQuux

此实现:

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

原因:

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

我热衷于 quux() 到 return 的参考,因为 99% 的实现 HasQuux 的结构都有一个 Quux 字段。

我明白为什么这是不可能的。

我可以创建一个临时的 Quux,其生命周期与 Bar 的生命周期相匹配,并且 return 是对它的引用吗?

或者有更好的解决方案吗?

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

简短的回答是:你不能。你可以做的是 quux return 一个 Cow (写时复制,而不是牛):

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

99% 有 self.q 的 impl 会 return Cow::Borrowed(&self.q),而 1% 没有 return Cow::Owned(self.calc_quux())。 (Playground.)

如果您不能更改特征定义,那么您不太可能return 安全 Rust 中的引用(没有 leaking Quux)。首先,您需要将计算出的 q 存储在 self 内的 Option<Quux> 中,这可能不是您想要的。但即使是这样,您也会遇到 HasQuux::quux 使用 &self 而不是 &mut self 的问题。使用 RefCell<Option<Quux>> 将允许您存储计算出的 Quux,但不能存储到 return 对它的引用,因为 Rust 不能阻止您覆盖 RefCell 中其他地方的内容代码。

如果缓存给出的&Quux是可以接受的,你可以实现一个辅助类型1来封装内部可变性:

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()
        }
    }
}

上述类型在其实现中使用了不安全,但提供了一个完全安全的接口(据我所知 - 不安全代码永远无法 100% 确定)。安全注释中提到的另一个borrow_mut()不能被外部代码执行,因为Lazy的字段是私有的。尽管如此,如果 Lazy 的定义被错误修改,编译器无法捕获错误,因为我们使用 unsafe 来说服它我们知道我们在做什么。

有了这个辅助类型,我们可以为 Bar:

提供一个简单而安全的 HasQuux 实现
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

或者只使用 OnceCell type from the once_cell crate whose get_or_init 方法完全等同于上面定义的 Lazy::ensure()