【问题标题】:Cannot return reference to local variable无法返回对局部变量的引用
【发布时间】:2021-04-17 06:52:00
【问题描述】:

我有一个特点:

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

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

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() 返回一个引用,因为 99% 的实现 HasQuux 的结构都有一个 Quux 字段。

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

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

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

【问题讨论】:

    标签: rust reference borrow-checker


    【解决方案1】:

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

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

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

    具有self.q 的 99% 的 impl 将返回 Cow::Borrowed(&amp;self.q),而没有 Cow::Owned(self.calc_quux()) 的 1% 将返回。 (Playground.)

    如果你不能改变 trait 定义,那么你就不太可能在安全的 Rust 中返回一个引用(没有leakingQuux)。首先,您需要将计算出的q 存储在Option&lt;Quux&gt; 内的self 中,这可能不是您想要的。但即使是这样,你也会遇到HasQuux::quux 接受&amp;self 而不是&amp;mut self 的问题。使用RefCell&lt;Option&lt;Quux&gt;&gt; 将允许您存储计算出的Quux,但不能返回对它的引用,因为Rust 无法阻止您覆盖代码中其他地方的RefCell 内容。

    如果缓存给出的&amp;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()
            }
        }
    }
    

    上述类型在其实现中使用了 unsafe,但提供了一个完全安全的接口(据我所知 - 永远不能 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

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

    【讨论】:

    • 谢谢,这是一个非常全面的答案。我不得不重新考虑我的结构,因为使用 Cow 在源的其余部分产生了太多噪音。
    最近更新 更多