无法返回对局部变量的引用

fad*_*bee 1 reference rust borrow-checker

我有一个特点:

pub trait HasQuux {
    fn quux(&self) -> &Quux;
}
Run Code Online (Sandbox Code Playgroud)

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

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

impl HasQuux for Foo {
    fn quux(&self) -> &Quux {
        &self.q
    }
}
Run Code Online (Sandbox Code Playgroud)

我如何实现HasQuux必须计算 Quux 的结构?

这个实现:

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

原因:

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
Run Code Online (Sandbox Code Playgroud)

我渴望quux()返回一个引用,因为 99% 的结构实现HasQuux都有一个Quux字段。

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

我可以创建一个Quux其生命周期与其生命周期相匹配的临时对象Bar并返回对其的引用吗?

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

use*_*342 5

我可以创建一个Quux其生命周期与 Bar 的生命周期匹配的临时对象并返回对其的引用吗?

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

fn quux(&self) -> Cow<'_, Quux>
Run Code Online (Sandbox Code Playgroud)

99% 具有 a 的 implsself.q将会返回Cow::Borrowed(&self.q),而 1% 没有的 impls 将会返回Cow::Owned(self.calc_quux())。(游乐场。

如果您无法更改特征定义,那么您不太可能成功返回安全 Rust 中的引用(不会泄漏 Quux)。首先,您需要将计算结果存储qOption<Quux>inside中self,这可能不是您想要的。HasQuux::quux但即使是这样,你也会遇到接受&self和不接受的问题&mut self。使用RefCell<Option<Quux>>将允许您存储计算的Quux,但不能返回对其的引用,因为 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()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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())
    }
}
Run Code Online (Sandbox Code Playgroud)

操场


1

或者只使用板条箱OnceCell中的类型once_cell,其get_or_init方法与上面定义的方法完全相同Lazy::ensure()