Rust,如何返回对某个结构体的引用,该结构体的寿命与结构体一样长?

时间:2019-07-15 23:46:25

标签: rust lifetime

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

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

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

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

我希望能够获得对HashMap中对象的只读引用,以便可以从其他数据结构中引用它。例如,当我解析一个函数调用时,我希望能够存储对正在调用的函数的引用,而不仅仅是存储函数的名称,并且每次我需要实际的{{ 1}}对象对应的名称。为此,我使用了以下方法:

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>的唯一位置是来自哈希映射,哈希映射定义为具有{ {1}}。我发现的另一个错误修复是:

Entity

这意味着Entity<'a>只能被调用一次,这显然是一个问题。我以前的理解是不正确的,但问题仍然在于需要引用{{1 }}具有与整个对象相同的生存期,从而严重限制了代码,因为我无法从具有较短生存期的引用中调用此方法,例如一个作为函数参数传入或一个在循环中创建。

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

1 个答案:

答案 0 :(得分:0)

这是您想要的签名:

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

这是为什么它不起作用的原因:它返回一个引用,该引用借用Entity的时间长于最初借用lookup的{​​{1}}的时间。 并非非法,但这意味着不能从Scope引用派生引用lookup。为什么?因为给出了以上签名,所以这是有效的代码:

self

这段代码compiles因为必须:没有生存期约束,编译器可以用来证明它是错误的,因为let sc = Scope { ... }; let foo = sc.lookup("foo"); drop(sc); do_something_with(foo); 的生存期与{{1 }}。但是很明显,如果以您第一次尝试的方式实现了foosc将在lookup之后包含一个悬空指针,这就是编译器拒绝它的原因。

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

  • 解耦drop(sc)中的生存期,以便借用lookup的生存期与Scope不同。然后让parent进入symbols这可能无法自行解决,具体取决于您需要对lookup进行哪些操作,但是如果您需要区分不同数据的生命周期,则可能仍然需要这样做。

    &'parent self
  • 将您的Entity和/或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借阅期满的引用。折衷方案是在整个竞技场被摧毁之前,竞技场中的任何东西都不会被释放。它不能替代垃圾收集。

  • 使用Entityself存储Rc和/或Arc和/或Scope存储的包含参考。这是完全摆脱生命周期参数的一种方法,但运行时成本却很小。