2

刚接触 Rust 并试图自学等等。我被困在一个终生的问题上。我能找到的最接近的已经发布的问题是:

参数要求 _ 被借用为 'static - 我该如何解决这个问题?

我正在玩的小项目定义了两个结构,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 {});
        }
    }
}

我写了一个单元测试,其中包括这一行

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这似乎是我的错误的根源,但在我之前链接的帖子中,作者能够通过调整包含引用的 Option<> 的生命周期来解决问题,据我所知。在我的示例中,我只是对test_item. 是否像其他作者那样包含推荐的方法?

生命'static周期意味着test_item只要单元测试正在运行,它将基本上存在?

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

感谢您的任何建议。

4

1 回答 1

6

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

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

什么是寿命?

首先,我假设您已经查阅了一些基本的 Rust 术语,例如借用和移动以及 rust 的所有权如何工作。如果不是,我强烈建议您阅读Rust Book中的了解所有权部分。

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

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

上面的代码将无法编译,因为对 x 的引用超过了变量。这意味着x当到达内部范围的末尾时将被放入,您将在外部范围中保存对它的引用。所以当你到达时,println!基本上你有一个对不再“存在”的变量的引用。

理解这一点的一种更简单的方法是说它的r寿命比它的寿命长x,因此您无法保存xinto的引用,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必须指向一个至少与'a(函数)一样长的变量。

大多数时候编译器会自动为您执行此操作,但其他时候您必须手动定义生命周期,例如在结构中。

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. 你已经注释Item'static生命周期。
  5. 然后你被迫'static在函数中传递一个引用,take_item因为有时你可以将项目保存在结构中,HashMap这现在需要.'staticItem

这意味着对 的引用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 的视频,其中解释了生命周期。

于 2022-01-11T23:29:52.857 回答