您的代码中的主要问题是您'static
在结构中使用了生命周期。
我将尝试解释什么是生命周期,它们是如何工作的,以及为什么你会遇到这个错误。我警告你,这会很长,你可能会有疑问,所以最后我将链接一个非常好的视频,其中精彩地解释了生命周期。
什么是寿命?
首先,我假设您已经查阅了一些基本的 Rust 术语,例如借用和移动以及 rust 的所有权如何工作。如果不是,我强烈建议您阅读Rust Book中的了解所有权部分。
所以基本上 rust 编译器使用生命周期来定义引用在程序中的生存时间。假设我们有以下代码(取自书中):
{
let r;
{
let x = 4;
r = &x;
}
println!("r: {}", r);
}
上面的代码将无法编译,因为对 x 的引用超过了变量。这意味着x
当到达内部范围的末尾时将被放入,您将在外部范围中保存对它的引用。所以当你到达时,println!
基本上你有一个对不再“存在”的变量的引用。
理解这一点的一种更简单的方法是说它的r
寿命比它的寿命长x
,因此您无法保存x
into的引用,r
因为在某些时候x
会死掉并且存储在 r 中的引用将是无效的。
为了跟踪这些错误,rust 编译器使用标识符。它们几乎可以有任何名称,前面带有'
. 有效的生命周期也是如此'a
,例如'potato
. Rust 中的所有引用都有一个生命周期,这取决于它们的生命周期(它们所在的范围)。
例如,在上面的代码中有两个生命周期:
{
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // |
} // ---------+
因此,'a
您'b
无法将&'b
引用保存到'a
生命周期中。
终身省略
现在你可能会问自己为什么不经常看到生命周期注释,这称为生命周期省略,是一个过程,在这个过程中 rust 编译器会为你做一些工作,这样你就可以专注于编程而不是注释所有引用你的程序。例如,给定以下函数:
fn takes_a_ref(name: &str) {
...
}
rust 编译器会自动为函数括号对应的作用域定义一个新的生命周期名称。您可以使用几乎任何名称对其进行注释,但为了简单起见,编译器使用字母表中的字母来定义新的生命周期名称。假设编译器选择了字母,'a
那么这个函数将被自动注释为:
fn takes_a_ref<'a>(name: &'a str) {
...
}
这意味着生命周期takes_a_ref
被调用'a
,并且您传递给的引用takes_a_ref
必须指向一个至少与'a
(函数)一样长的变量。
大多数时候编译器会自动为您执行此操作,但其他时候您必须手动定义生命周期,例如在结构中。
pub struct MyStruct {
pub field: &str
}
// Does not compile
应注释为:
pub struct MyStruct<'a> {
pub field: &'a str,
}
特殊的终生名称
您可能已经注意到,在提及命名生命周期的可能性时,我一直在谈论几乎所有名称。这是因为存在一些具有特殊含义的保留生命周期名称:
生命'static
周期是对应于程序整个生命周期的生命周期。这意味着,为了获得具有'static
生命周期的引用,它指向的变量必须从程序启动到完成为止。一个例子是const
变量:
const MY_CONST: &str = "Hello! "; // Here MY_CONST has an elided static lifetime
生命'_
周期称为匿名生命周期,它只是一个标记,表明变量中存在生命周期省略。它将被编译器编译时替换,它仅用于澄清海豚。
你的代码有什么问题?
所以你遇到了以下情况:
- 您创建了一个名为的结构
Agent
,其中包含一个HashMap
.
- 这
HashMap
包含一个拥有的String
和一个对Item
.
- 编译器告诉您必须指定 的生命周期,
Item
因为编译器不会忽略结构中的生命周期。
- 你已经注释
Item
了'static
生命周期。
- 然后你被迫
'static
在函数中传递一个引用,take_item
因为有时你可以将项目保存在结构中,HashMap
这现在需要.'static
Item
这意味着对 的引用Item
必须指向一个Item
在整个程序中都存在的实例。例如:
fn function() {
let mut agent = Agent::new();
let my_item = Item::new();
let result = agent.take_item(&item);
...
}
fn main() {
function();
// Do some other stuff. The scope of 'function' has ended and the variables dropped but the program has not ended! 'my_item' does not live for the entirety of the program.
}
你不需要my_item
活得像整个程序一样长,你需要my_item
活得一样长Agent
。这适用于将存储在 . 中的任何引用,Agent
只要它存在Agent
.
解决方案(选项 1)
Agent
使用不是生命周期的生命周期进行注释'static
,例如:
pub struct Agent<'a> {
pub items: HashMap<String, &'a Item>,
}
impl <'a> Agent<'a> {
pub fn take_item(&mut self, item: &'a Item) -> std::result::Result<(), TestError> {
...
}
}
这意味着只要参考点所在的实例与Agent
存储它的特定实例一样长或更长,就不会有问题。在take_item
您指定的函数中:
参考点必须等于或长于 this 的变量的生命周期Agent
。
fn function() {
let mut agent = Agent::new();
let my_item = Item::new();
let result = agent.take_item(&item);
...
}
fn main() {
function();
// No problem
}
现在可以正常编译了。
请记住,您可能必须开始注释函数才能强制项目与代理一样长。
阅读本书中有关生命周期的更多信息。
解决方案(选项 2)
您是否真的需要将该项目作为参考存储在Agent
? 如果答案是否定的,那么您可以将所有权传递Item
给代理:
pub struct Agent {
pub items: HashMap<String, Item>,
}
在实现中,函数生命周期会自动省略,以与函数一样长:
pub fn take_item(&mut self, item: &Item) {
...
}
所以就是这样。在这里,您有一个来自 YouTube 频道 Let's Get Rusty 的视频,其中解释了生命周期。