借用变量时的 Rust 生命周期语法

Rust lifetime syntax when borrowing variables

Rust 的新手并试图自学等等。我被困在一生的问题上。我能找到的最接近的问题已经 posted 是:

我正在玩的小项目定义了两个结构,AgentItem

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 {});
        }
    }
}

我写了一个包含这一行的单元测试

let result = test_agent.take_item(&test_item);

我知道某处有错误,因为编译器告诉我:

  --> 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`
...

我将 test_item 作为对 take_item() 的引用传递(或者更确切地说,如果我正确使用行话,take_item() 是“借用”test_item) .这似乎是我错误的根源,但在我链接的早期 post 中,作者能够通过调整包含引用的 Option<> 的生命周期来解决问题,据我所知。在我的示例中,我只使用了对 test_item 的简单引用。像其他作者那样包含它,是推荐的方法吗?

'static 生命周期意味着 test_item 本质上只要单元测试是 运行?

就会存在

我认为我的主要问题归结为,必须使用什么语法 take_item() 借用 test_item 才能使我的代码正确?我是否正确地考虑了这一点?

感谢任何建议。

您代码中的主要问题是您在结构中使用了 'static 生命周期。

我将尝试解释什么是生命周期、它们是如何工作的以及为什么您会遇到此错误。我警告你,这会很长,你可能会有疑问,所以最后我会 link 一个非常好的视频,其中对生命周期进行了精彩的解释。

什么是生命周期?

首先,我假设您已经查阅了一些基本的 Rust 术语,例如借用和移动以及 Rust 的所有权如何运作。如果不是,我强烈建议您阅读 Understanding Ownership section in the Rust Book.

所以基本上,rust 编译器使用生命周期来定义引用在您的程序中存在多长时间。假设我们有以下代码(摘自书中):

{
    let r;
    {
        let x = 4;
        r = &x;
    }
    println!("r: {}", r);
}

以上代码无法编译,因为对 x 的 引用比变量 还长。这意味着虽然 x 将在到达内部范围的末尾时被丢弃,但您正在外部范围中保存对它的引用。所以当你到达 println! 时,基本上你有一个对不再“存在”的变量的引用。

一个更容易理解的方法是说 rx 寿命长,因此您不能将 x 的引用保存到 r 中,因为在某些情况下点 x 死亡 并且存储在 r 中的引用将无效。

  • r x
  • 寿命更长
  • r 活不过 x

为了跟踪这些错误,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 的引用必须指向一个至少与 [=35] 一样长的变量=](函数)。

大部分时间编译器会自动为您完成此操作,但其他时候您必须手动定义生命周期,例如在结构中。

pub struct MyStruct {
    pub field: &str
}
// Does not compile

应注释为:

pub struct MyStruct<'a> {
    pub field: &'a str,
}

一生的特殊名字

您可能已经注意到,在提到命名生命周期的可能性时,我一直在谈论几乎任何名称。这是因为存在一些具有特殊含义的保留生命周期名称:

  • 'static
  • '_

'static生命周期是对应程序整个生命周期的生命周期。这意味着为了获得具有 'static 生命周期的引用,它指向的变量必须从程序启动到完成为止一直存在。一个例子是 const 变量:

const MY_CONST: &str = "Hello! "; // Here MY_CONST has an elided static lifetime

'_生命周期称为匿名生命周期,它只是一个标记,表明变量中存在生命周期省略。它将被编译器编译时替换它只服务于澄清海豚。

你的代码有什么问题?

所以您遇到了以下情况:

  1. 您创建了一个名为 Agent 的结构,其中包含一个 HashMap.
  2. HashMap 包含拥有的 String 和对 Item 的引用。
  3. 编译器告诉您必须指定 Item 的生命周期,因为编译器不会省略结构中的生命周期。
  4. 您已用 'static 生命周期注释 Item
  5. 然后你被迫在 take_item 函数中传递一个 'static 引用,因为有时你可能将项目保存在结构的 HashMap 中,现在需要一个 'static Item.
  6. 的生命周期

这意味着对 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_itemAgent 一样长。这适用于将存储在 Agent 中的任何引用,只要它在 Agent.

中存在就可以。

解决方案(方案一)

用不是'static生命周期的生命周期注释Agent,例如:

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 函数中指定:

The lifetime of the variable where the reference points must be equal or longer than 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 
}

现在可以正常编译了。

请记住,您可能必须开始注释函数才能强制该项目与 Agent 一样长。

Read more about lifetimes in the book.

解决方案(方案二)

您真的需要将项目作为参考存储在 Agent 中吗?如果答案是否定的,那么您可以将 Item 的所有权传递给代理人:

pub struct Agent {
    pub items: HashMap<String, Item>,
}

在实现中,函数生命周期被自动省略,只要函数存在就可以:

pub fn take_item(&mut self, item: &Item) {
    ...
}

就是这样。 Here you have a video 来自 YouTube 频道 Let's Get Rusty,其中解释了生命周期。