Rust,如何返回对某个结构体的引用,该结构体的寿命与结构体一样长? [英] Rust, how to return reference to something in a struct that lasts as long as the struct?

查看:39
本文介绍了Rust,如何返回对某个结构体的引用,该结构体的寿命与结构体一样长?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am porting a compiler I wrote to Rust. In it, I have an enum Entity which represents things like functions and variables:

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

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

I then have a struct Scope which is responsible for holding on to these entities in a hash map, where the key is the name given by the programmer to the entity. (For example, declaring a function named sin would put an Entity into the hash map at the key sin.)

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

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

I would like to be able to get read-only references to the objects in the HashMap so that I can refer to it from other data structures. For example, when I parse a function call, I want to be able to store a reference to the function that is being called instead of just storing the name of the function and having to look up the reference every time I need the actual Entity object corresponding to the name. To do so, I have made this method:

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

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

However, this results in a compilation error:

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<'_>>

我尝试过的事情

有几种方法可以使编译错误消失,但是没有一种方法可以实现我想要的行为.首先,我可以这样做:

Things I Tried

There are several ways to make the compilation error go away, but none of them give the behavior I want. First, I can do this:

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

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

But this means the reference will not live long enough, so I can't put it into a struct or any other kind of storage that will outlive the scope that lookup is called from. Another solution was this:

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

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

Which I do not understand why it could compile. As part of the struct definition, things inside Entity objects in the hash map must live at least as long as the scope, so how can the compiler allow the return type to be missing that? Additionally, why would the addition of <'a> result in the previous compiler error, since the only place the function is getting Entitys from is from the hash map, which is defined as having a value type of Entity<'a>. Another bad fix I found was:

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

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

Which would mean that lookup can only be called once, which is obviously a problem. My previous understanding was incorrect, but the problem still remains that requiring the reference to self to have the same lifetime as the whole object severely restricts the code in that I can't call this method from a reference with any shorter lifetime, e.g. one passed in as a function argument or one created in a loop.

推荐答案

以下是您想要的签名:

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

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

Here's why it can't work: it returns a reference that borrows an Entity for longer than lookup initially borrowed the Scope. This isn't illegal, but it means that the reference lookup returns can't be derived from the self reference. Why? Because given the above signature, this is valid code:

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

此代码编译,因为它必须:没有生存期约束,编译器可以用来证明它是错误的,因为 foo 的生​​存期不与 sc 的借用相关.但是很明显,如果 lookup 是按照您第一次尝试的方式实现的,则 foo 将在 drop(sc)之后包含一个悬空指针,这就是为什么编译器拒绝了它.

This code compiles because it has to: there is no lifetime constraint that the compiler could use to prove it wrong, because the lifetime of foo isn't coupled to the borrow of sc. But clearly, if lookup were implemented the way you first tried, foo would contain a dangling pointer after drop(sc), which is why the compiler rejected it.

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

You must redesign your data structures to make the given signature for lookup work. It's not clear how best to do this given the code in the question, but here are some ideas:

  • 解耦 Scope 中的生存期,以便借用 parent 的生​​存期与 symbols 不同.然后让 lookup 选择&'父母本人.这可能无法自行解决,具体取决于您需要对 Entity 进行哪些操作,但是如果您需要区分生命周期,则可能仍需要这样做不同的数据.

  • Decouple the lifetimes in Scope so that the parent is borrowed for a different lifetime than the symbols. Then have lookup take &'parent self. This probably will not work by itself, depending on what you need to do with the Entitys, but you may need to do it anyway if you need to distinguish between the lifetimes of different data.

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存储在竞技场中.只要竞技场数据结构本身不超过竞技场数据结构的长度,竞技场就可以提供超出 self -借项寿命的引用.折衷方案是在整个竞技场被摧毁之前,竞技场中的任何东西都不会被释放.它不能替代垃圾收集.

  • Store your Scopes and/or your Entitys in an arena. An arena can give out references that outlive the self-borrow, as long as they don't outlive the arena data structure itself. The tradeoff is that nothing in the arena will be deallocated until the whole arena is destroyed. It's not a substitute for garbage collection.

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

    Use Rc or Arc to store your Scopes and/or your Entitys and/or whatever data Entity stores that contains references. This is one way to get rid of the lifetime parameter completely, but it comes with a small runtime cost.

    这篇关于Rust,如何返回对某个结构体的引用,该结构体的寿命与结构体一样长?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆