我正在将我编写的编译器移植到 Rust。在其中,我有一个枚举Entity,它代表函数和变量之类的东西:
pub enum Entity<'a> {
Variable(VariableEntity),
Function(FunctionEntity<'a>)
// Room for more later.
}
Run Code Online (Sandbox Code Playgroud)
然后,我有一个结构体Scope,负责在哈希映射中保存这些实体,其中键是程序员为实体指定的名称。(例如,声明一个名为 name 的函数sin会将 an 放Entity入哈希映射中的 key 处sin。)
pub struct Scope<'a> {
symbols: HashMap<String, Entity<'a>>,
parent: Option<&'a Scope<'a>>
}
Run Code Online (Sandbox Code Playgroud)
我希望能够获得对 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
}
}
}
Run Code Online (Sandbox Code Playgroud)
但是,这会导致编译错误:
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<'_>>
Run Code Online (Sandbox Code Playgroud)
有多种方法可以消除编译错误,但没有一种方法可以提供我想要的行为。首先,我可以这样做:
pub fn lookup(&self, symbol: &str) -> Option<&Entity<'a>> {
Run Code Online (Sandbox Code Playgroud)
但这意味着引用的寿命不会足够长,因此我无法将其放入结构或任何其他类型的存储中,这些存储将比调用的作用域寿命更长lookup。另一个解决方案是这样的:
pub fn lookup(&self, symbol: &str) -> Option<&'a Entity> {
Run Code Online (Sandbox Code Playgroud)
我不明白为什么它可以编译。作为结构定义的一部分,Entity哈希映射中对象内部的内容必须至少与作用域一样长,那么编译器如何允许返回类型丢失呢?此外,为什么添加 会导致<'a>前面的编译器错误,因为该函数获取Entitys 的唯一位置是来自哈希映射,该映射被定义为具有值类型Entity<'a>。我发现的另一个不好的修复是:
pub fn lookup(&'a self, symbol: &str) -> Option<&'a Entity<'a>> {
Run Code Online (Sandbox Code Playgroud)
这意味着我之前的理解是不正确的,但问题仍然存在,要求引用lookup只能被调用一次,这显然是一个问题。self与整个对象具有相同的生命周期严重限制了代码,因为我无法从生命周期较短的引用(例如传入的引用)调用此方法作为函数参数或在循环中创建的参数。
这是您想要的签名:
pub fn lookup(&self, symbol: &str) -> Option<&'a Entity<'a>>
Run Code Online (Sandbox Code Playgroud)
这就是它不起作用的原因:它返回一个借用 的时间比最初借用 的时间Entity更长的引用。这不是非法的,但它意味着引用返回不能从引用派生。为什么?因为给出了上面的签名,所以这是有效的代码:lookupScopelookupself
let sc = Scope { ... };
let foo = sc.lookup("foo");
drop(sc);
do_something_with(foo);
Run Code Online (Sandbox Code Playgroud)
该代码可以编译,因为它必须这样做:编译器没有可以用来证明其错误的生命周期约束,因为 的生命周期foo不与 的借用耦合sc。但显然,如果按照lookup您第一次尝试的方式实现,foo则会在 后包含一个悬空指针drop(sc),这就是编译器拒绝它的原因。
您必须重新设计数据结构才能使给定的签名正常lookup工作。鉴于问题中的代码,尚不清楚如何最好地做到这一点,但这里有一些想法:
将 的生命周期解耦,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>> {
/* ... */
}
}
Run Code Online (Sandbox Code Playgroud)Scope将您的s 和/或您的s存放Entity在竞技场中。arena 可以给出比self-borrow 存活时间更长的引用,只要它们不比 arena 数据结构本身存活得更久。代价是,在整个竞技场被摧毁之前,竞技场中的任何内容都不会被释放。它不能替代垃圾收集。
使用Rc或Arc来存储您的Scope和/或您的Entity和/或Entity包含引用的任何数据存储。这是完全摆脱生命周期参数的一种方法,但它的运行时间成本很小。