gro*_*egg 6 reference lifetime rust borrow
Rust 新手,试图自学等等。我陷入了一生的问题。我能找到的最接近的已发布问题是:
参数要求借用 _ 来表示“静态” - 我该如何解决这个问题?
我正在玩的小项目定义了两个结构,Agent
并且Item
。
Agent
除其他内容外,该结构还包含以下行:
pub inventory: HashMap<String, &'static Item>,
此外,我还实现了这段代码:
impl Agent {
pub fn take_item(&mut self, item: &'static Item) -> std::result::Result<(), TestError> {
if item.can_be_taken {
if self.can_reach_item(item) {
self.inventory.insert(item.name.to_string(), item);
return Ok(());
} else {
return Err(ItemOutOfReachError {});
}
} else {
return Err(ItemCannotBeTakenError {});
}
}
}
Run Code Online (Sandbox Code Playgroud)
我写了一个单元测试,其中包括这一行
let result = test_agent.take_item(&test_item);
Run Code Online (Sandbox Code Playgroud)
我知道某个地方有错误,因为编译器告诉我:
--> src/agent.rs:57:47
|
57 | let result = test_agent.take_item(&test_item);
| ---------------------^^^^^^^^^^-
| | |
| | borrowed value does not live long enough
| argument requires that `test_item` is borrowed for `'static`
...
Run Code Online (Sandbox Code Playgroud)
我将其test_item
作为参考传递take_item()
(或者更确切地说,如果我正确使用术语,则为take_item()
“借用” )。test_item
这似乎是我的错误的根源,但在我链接的之前的帖子中,作者能够通过调整包含引用的 Option<> 的生命周期来解决问题,据我所知。在我的示例中,我仅使用对test_item
. 包含它,就像其他作者所做的那样,是推荐的方法吗?
生命周期'static
意味着test_item
只要单元测试正在运行,该生命周期就基本上存在?
我认为我的主要问题归结为,必须take_item()
借用什么语法test_item
才能使我的代码正确?我的想法正确吗?
感谢您的任何建议。
Pau*_*AVA 18
代码中的主要问题是您'static
在结构中使用了生命周期。
我将尝试解释什么是生命周期、它们如何工作以及为什么您会遇到此错误。我警告你,这会很长,你可能会有疑问,所以最后我将链接一个非常好的视频,其中对生命周期进行了精彩的解释。
什么是生命周期?
首先,我假设您已经查找了一些基本的 Rust 术语,例如借用和移动以及 Rust 的所有权如何运作。如果没有,我强烈建议您阅读Rust Book中的理解所有权部分。
因此,基本上,Rust 编译器使用生命周期来定义引用在程序中的生存时间。假设我们有以下代码(摘自书中):
{
let r;
{
let x = 4;
r = &x;
}
println!("r: {}", r);
}
Run Code Online (Sandbox Code Playgroud)
上面的代码不会编译,因为对 x 的引用比变量的寿命长。这意味着x
当到达内部作用域的末尾时, while 将被删除,您将在外部作用域中保存对它的引用。因此,当您到达基本上时,println!
您将引用一个不再“存在”的变量。
理解这一点的一种更简单的方法是,r
生命周期长于,因此您无法保存intox
的引用,因为在某个时刻将会死亡,并且存储在 r 中的引用将无效。x
r
x
r
寿命比 x
r
比寿命长 x
为了跟踪这些错误,Rust 编译器使用标识符。它们几乎可以有任何前面带有'
. 'a
有效的生命周期也是如此,例如'potato
。Rust 中的所有引用都有一个生命周期,该生命周期由它们的生存时间(它们所在的范围)决定。
例如,在上面的代码中有两个生命周期:
{
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // |
} // ---------+
Run Code Online (Sandbox Code Playgroud)
因此,当生命'a
周期结束时'b
,您无法将&'b
引用保存到生命周期中'a
。
终身省略
现在你可能会问自己为什么你不经常看到生命周期注释,这称为生命周期省略,这是一个 rust 编译器为你做一些工作的过程,这样你就可以专注于编程而不是注释中的所有引用你的程序。例如,给定以下函数:
fn takes_a_ref(name: &str) {
...
}
Run Code Online (Sandbox Code Playgroud)
rust 编译器会自动为函数括号对应的作用域定义一个新的生命周期名称。您可以使用几乎任何名称来注释它,但为了简单起见,编译器使用字母表中的字母来定义新的生命周期名称。假设编译器选择了字母,'a
那么该函数将自动注释为:
fn takes_a_ref<'a>(name: &'a str) {
...
}
Run Code Online (Sandbox Code Playgroud)
takes_a_ref
这意味着调用了 的生命周期'a
,并且传递给的引用必须指向一个至少与(函数)takes_a_ref
一样长的变量。'a
大多数情况下,编译器会自动为您执行此操作,但有时您必须手动定义生命周期,例如在结构中。
pub struct MyStruct {
pub field: &str
}
// Does not compile
Run Code Online (Sandbox Code Playgroud)
应注释为:
pub struct MyStruct<'a> {
pub field: &'a str,
}
Run Code Online (Sandbox Code Playgroud)
特殊的一生名字
您可能已经注意到,在提到命名生命周期的可能性时,我几乎讨论了任何名称。这是因为存在一些具有特殊含义的保留生命周期名称:
'static
'_
生命'static
周期是与程序的整个生命周期相对应的生命周期。这意味着,为了获得具有'static
生命周期的引用,它指向的变量必须从程序启动到结束都有生命周期。一个例子是const
变量:
const MY_CONST: &str = "Hello! "; // Here MY_CONST has an elided static lifetime
Run Code Online (Sandbox Code Playgroud)
生命'_
周期称为匿名生命周期,它只是一个标记,表明变量中存在生命周期省略。它将被编译器取代,编译时它只起到澄清作用。
你的代码有什么问题吗?
那么你遇到过以下情况:
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.
}
Run Code Online (Sandbox Code Playgroud)
你不需要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> {
...
}
}
Run Code Online (Sandbox Code Playgroud)
这意味着只要参考点所在的实例的寿命与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
}
Run Code Online (Sandbox Code Playgroud)
现在可以正常编译了。
请记住,您可能必须开始注释函数才能强制该项目与代理一样长。
解决方案(选项2)
您实际上需要将该项目存储为 中的参考吗Agent
?如果答案是否定的,那么您可以将 的所有权传递Item
给代理:
pub struct Agent {
pub items: HashMap<String, Item>,
}
Run Code Online (Sandbox Code Playgroud)
在实现中,函数生命周期会自动被忽略,以与函数一样长地存在:
pub fn take_item(&mut self, item: &Item) {
...
}
Run Code Online (Sandbox Code Playgroud)
所以就是这样。这里有来自 YouTube 频道 Let's Get Rusty 的视频,其中解释了生命周期。