Rust,如何 return 引用结构中持续时间与结构一样长的内容?
Rust, how to return reference to something in a struct that lasts as long as the struct?
我正在移植我写给 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 中对象的只读引用,以便我可以从其他数据结构中引用它。例如,当我解析一个函数调用时,我希望能够存储对被调用函数的引用,而不是只存储函数的名称并且每次需要实际 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<'_>>
我尝试过的事情
有多种方法可以消除编译错误,但其中 none 给出了我想要的行为。首先,我可以这样做:
pub fn lookup(&self, symbol: &str) -> Option<&Entity<'a>> {
但这意味着引用不会存在足够长的时间,所以我不能将它放入结构或任何其他类型的存储中,这些存储将超过调用 lookup
的范围。另一个解决方案是:
pub fn lookup(&self, symbol: &str) -> Option<&'a Entity> {
我不明白为什么它可以编译。作为结构定义的一部分,散列映射中 Entity
对象内的东西必须至少与范围一样长,那么编译器怎么能允许 return 类型丢失呢?此外,为什么添加 <'a>
会导致先前的编译器错误,因为函数从中获取 Entity
s 的唯一位置是来自散列映射,它被定义为具有值类型 Entity<'a>
。我发现的另一个错误修复是:
pub fn lookup(&'a self, symbol: &str) -> Option<&'a Entity<'a>> {
也就是说lookup
只能调用一次,这显然是个问题。我之前的理解是错误的,但问题仍然是要求引用 self
与整个对象具有相同的生命周期严重限制了代码,因为我不能从生命周期更短的引用调用此方法,例如一种作为函数参数传入,一种在循环中创建。
我该如何解决这个问题?有什么方法可以修复我现在拥有的功能,还是我需要以完全不同的方式实现我正在寻找的行为?
这是你想要的签名:
pub fn lookup(&self, symbol: &str) -> Option<&'a Entity<'a>>
这就是它不起作用的原因:它 returns 借用 Entity
的时间超过 lookup
的引用最初借用了 Scope
。这 不是非法的 ,但它意味着引用 lookup
returns 不能从 self
引用派生。为什么?因为给出上面的签名,这是有效的代码:
let sc = Scope { ... };
let foo = sc.lookup("foo");
drop(sc);
do_something_with(foo);
这段代码 compiles 因为它必须:没有编译器可以用来证明它错误的生命周期约束,因为 foo
的生命周期没有耦合到 sc
。但显然,如果 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
and/or 您的 Entity
存储在竞技场中。 arena 可以给出比 self
-borrow 还长的引用,只要它们不比 arena 数据结构本身长。权衡是 nothing 在竞技场中将被释放,直到整个竞技场被摧毁。它不能替代垃圾收集。
使用Rc
或Arc
来存储你的Scope
s and/or你的Entity
s and/or任何数据Entity
个包含引用的商店。这是完全摆脱生命周期参数的一种方法,但它的运行时间成本很小。
我正在移植我写给 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 中对象的只读引用,以便我可以从其他数据结构中引用它。例如,当我解析一个函数调用时,我希望能够存储对被调用函数的引用,而不是只存储函数的名称并且每次需要实际 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<'_>>
我尝试过的事情
有多种方法可以消除编译错误,但其中 none 给出了我想要的行为。首先,我可以这样做:
pub fn lookup(&self, symbol: &str) -> Option<&Entity<'a>> {
但这意味着引用不会存在足够长的时间,所以我不能将它放入结构或任何其他类型的存储中,这些存储将超过调用 lookup
的范围。另一个解决方案是:
pub fn lookup(&self, symbol: &str) -> Option<&'a Entity> {
我不明白为什么它可以编译。作为结构定义的一部分,散列映射中 Entity
对象内的东西必须至少与范围一样长,那么编译器怎么能允许 return 类型丢失呢?此外,为什么添加 <'a>
会导致先前的编译器错误,因为函数从中获取 Entity
s 的唯一位置是来自散列映射,它被定义为具有值类型 Entity<'a>
。我发现的另一个错误修复是:
pub fn lookup(&'a self, symbol: &str) -> Option<&'a Entity<'a>> {
也就是说我之前的理解是错误的,但问题仍然是要求引用 lookup
只能调用一次,这显然是个问题。self
与整个对象具有相同的生命周期严重限制了代码,因为我不能从生命周期更短的引用调用此方法,例如一种作为函数参数传入,一种在循环中创建。
我该如何解决这个问题?有什么方法可以修复我现在拥有的功能,还是我需要以完全不同的方式实现我正在寻找的行为?
这是你想要的签名:
pub fn lookup(&self, symbol: &str) -> Option<&'a Entity<'a>>
这就是它不起作用的原因:它 returns 借用 Entity
的时间超过 lookup
的引用最初借用了 Scope
。这 不是非法的 ,但它意味着引用 lookup
returns 不能从 self
引用派生。为什么?因为给出上面的签名,这是有效的代码:
let sc = Scope { ... };
let foo = sc.lookup("foo");
drop(sc);
do_something_with(foo);
这段代码 compiles 因为它必须:没有编译器可以用来证明它错误的生命周期约束,因为 foo
的生命周期没有耦合到 sc
。但显然,如果 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
and/or 您的Entity
存储在竞技场中。 arena 可以给出比self
-borrow 还长的引用,只要它们不比 arena 数据结构本身长。权衡是 nothing 在竞技场中将被释放,直到整个竞技场被摧毁。它不能替代垃圾收集。使用
Rc
或Arc
来存储你的Scope
s and/or你的Entity
s and/or任何数据Entity
个包含引用的商店。这是完全摆脱生命周期参数的一种方法,但它的运行时间成本很小。