首页 > 解决方案 > Rust,如何返回对结构中与结构一样长的东西的引用?

问题描述

我正在移植一个我写给 Rust 的编译器。在其中,我有一个枚举Entity,它代表函数和变量之类的东西:

pub enum Entity<'a> {
  Variable(VariableEntity),
  Function(FunctionEntity<'a>)
  // Room for more later.
}

然后我有一个结构Scope,它负责在哈希映射中保留这些实体,其中键是程序员给实体的名称。(例如,声明一个名为的函数sin会将 an 放Entity入哈希映射中的 key sin。)

pub struct Scope<'a> {
    symbols: HashMap<String, Entity<'a>>,
    parent: Option<&'a Scope<'a>>
}

我希望能够获得对 HashMap 中对象的只读引用,以便我可以从其他数据结构中引用它。例如,当我解析函数调用时,我希望能够存储对正在调用的函数的引用,而不是仅仅存储函数的名称并且每次需要Entity对应的实际对象时都必须查找引用到名字。为此,我制作了这种方法:

impl<'a> Scope<'a> {
  pub fn lookup(&self, symbol: &str) -> Option<&'a Entity<'a>> {
    let result = self.symbols.get(symbol);
    match result {
      Option::None => match self.parent {
        Option::None => Option::None,
        Option::Some(parent) => parent.lookup(symbol),
      },
      Option::Some(_value) => result
    }
  }
}

但是,这会导致编译错误:

error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
  --> src/vague/scope.rs:29:31
   |
29 |     let result = self.symbols.get(symbol);
   |                               ^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 28:3...
  --> src/vague/scope.rs:28:3
   |
28 | /   pub fn lookup(&self, symbol: &str) -> Option<&'a Entity<'a>> {
29 | |     let result = self.symbols.get(symbol);
30 | |     match result {
31 | |       Option::None => match self.parent {
...  |
36 | |     }
37 | |   }
   | |___^
note: ...so that reference does not outlive borrowed content
  --> src/vague/scope.rs:29:18
   |
29 |     let result = self.symbols.get(symbol);
   |                  ^^^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime 'a as defined on the impl at 9:6...
  --> src/vague/scope.rs:9:6
   |
9  | impl<'a> Scope<'a> {
   |      ^^
   = note: ...so that the expression is assignable:
           expected std::option::Option<&'a vague::entity::Entity<'a>>
              found std::option::Option<&vague::entity::Entity<'_>>

我尝试过的事情

有几种方法可以使编译错误消失,但它们都没有给出我想要的行为。首先,我可以这样做:

  pub fn lookup(&self, symbol: &str) -> Option<&Entity<'a>> {

但这意味着引用的寿命不够长,因此我不能将其放入结构或任何其他类型的存储中,这些存储将超过lookup被调用的范围。另一个解决方案是:

  pub fn lookup(&self, symbol: &str) -> Option<&'a Entity> {

我不明白为什么它可以编译。作为结构定义的一部分,Entity哈希映射中对象内部的东西必须至少与范围一样长,那么编译器如何允许返回类型丢失呢?此外,为什么添加会<'a>导致先前的编译器错误,因为函数获取Entitys 的唯一位置是来自哈希映射,它被定义为具有Entity<'a>. 我发现的另一个错误修复是:

  pub fn lookup(&'a self, symbol: &str) -> Option<&'a Entity<'a>> {

这意味着lookup只能调用一次,这显然是一个问题。我之前的理解是不正确的,但问题仍然存在,要求引用self与整个对象具有相同的生命周期严重限制了代码,因为我不能从生命周期较短的引用中调用此方法,例如传入的引用作为函数参数或在循环中创建的参数。

我该如何解决这个问题?有什么方法可以修复我现在拥有的功能,还是我需要以完全不同的方式实现我正在寻找的行为?

标签: rustlifetime

解决方案


这是你想要的签名:

pub fn lookup(&self, symbol: &str) -> Option<&'a Entity<'a>>

这就是它不能工作的原因:它返回一个借用 an 的引用Entitylookup最初借用的Scope. 这不是非法的,但这意味着引用lookup返回不能从self引用派生。为什么?因为给定了上面的签名,这是有效的代码:

let sc = Scope { ... };
let foo = sc.lookup("foo");
drop(sc);
do_something_with(foo);

这段代码之所以能够编译,是因为它必须:没有生命周期约束可供编译器用来证明它是错误的,因为 的生命周期foo与借用的sc. 但很明显,如果lookup按照您第一次尝试的方式实现,foo将在 之后包含一个悬空指针drop(sc),这就是编译器拒绝它的原因。

您必须重新设计数据结构以使给定的签名lookup正常工作。鉴于问题中的代码,目前尚不清楚如何最好地做到这一点,但这里有一些想法:

  • 解耦 in 的生命周期,Scope以便parent借用与 不同的生命周期symbols。然后有lookup&'parent self这可能不会单独工作,具体取决于您需要对Entitys 做什么,但如果您需要区分不同数据的生命周期,您可能仍需要这样做。

    pub struct Scope<'parent, 'sym> {
        symbols: HashMap<String, Entity<'sym>>,
        parent: Option<&'parent Scope<'parent, 'sym>>,
    }
    
    impl<'parent, 'sym> Scope<'parent, 'sym> {
        pub fn lookup(&'parent self, symbol: &str) -> Option<&'parent Entity<'sym>> {
            /* ... */
        }
    }
    
  • 将您Scope的 s 和/或您Entity的 s 存储在竞技场中。一个 arena 可以给出比self-borrow 寿命更长的引用,只要它们不超过 arena 数据结构本身。权衡是在整个竞技场被摧毁之前,竞技场中的任何内容都不会被释放。它不能替代垃圾收集。

  • 使用RcorArc存储您Scope的 s 和/或您的 s 和/或任何包含引用Entity的数据存储。Entity这是完全摆脱生命周期参数的一种方法,但它的运行时成本很小。


推荐阅读