我的变量的生命周期是否由于添加了一个明显无关的指令而改变了?

Is my variable's lifetime changing due to the addition of an apparently unrelated instruction?

我是 Rust 的新手,并且仍在阅读 the book,同时不时编写一些简单的程序来测试我正在学习的内容。

今天,我尝试编写一个建议作为练习的程序(更准确地说是 the end of chapter 8.3 上的最后一个)。由于我仍在学习,因此速度很慢,所以我 运行 为我添加到 main.rs 的几乎所有新行添加了一个新的 cargo build。截至目前,它看起来像这样:

use std::io::{self, Write};
use std::collections::{HashMap, HashSet};

enum Command<'a> {
    Add {name: &'a str, unit: &'a str},
    List {unit: &'a str},
    Exit
}

fn main() {
    let mut units: HashMap<&str, HashSet<&str>> = HashMap::new();

    loop {
        let mut cmd = String::new();
        io::stdin().read_line(&mut cmd).unwrap();

        let cmd = match parse_command(&cmd) {
            Ok(command) => command,
            Err(error) => {
                println!("Error: {}!", error);
                continue;
            }
        };

        match cmd {
            Command::Add {name: new_name, unit: new_unit} => {
                let mut u = units.entry("unit1").or_insert(HashSet::new());
                u.insert(new_name);
            },

            Command::List {unit: target_unit} => {},
            Command::Exit => break
        }
    } // end of loop
} // end of main

fn parse_command<'a>(line: &'a String) -> Result<Command<'a>, &'a str> {
    Ok(Command::Exit)
    // ... still need to write something useful ...
}

没什么复杂的,因为我什至还没有在我的 parse_command 函数中写任何东西,目前只有 returns 和 Result::Ok(Command::Exit),但是当我尝试编译上面的代码时代码,我收到以下错误:

error[E0597]: `cmd` does not live long enough
  --> src/main.rs:34:2
   |
17 |            let cmd = match parse_command(&cmd) {
   |                                           --- borrow occurs here
...
34 |    } // end of loop
   |    ^ `cmd` dropped here while still borrowed
35 | } // end of main
   | -  borrowed value needs to live until here

弄清楚应该没什么奇怪的,但我对这个错误很困惑。是的,我在loop的末尾丢了cmd,没问题,但是为什么借来的值要活到main的末尾?cmd 相关的任何事情都发生在 loop 内部,为什么借来的价值预期会比那更长寿?

为了找出问题所在,我删除了 Command::Add {...}match 臂内的两条线,所以它看起来像这样:

    match cmd {
        Command::Add {name: new_name, unit: new_unit} => {},
        Command::List {unit: target_unit} => {},
        Command::Exit => break
    }

而且,令我惊讶的是,代码编译没有错误(尽管我需要这些行,所以这只是一个愚蠢的测试)。

我认为这两行与我的 cmd 变量没有任何关系,是吗?这是怎么回事? 我 99% 确定我遗漏了一些非常愚蠢的东西,但我自己无法弄清楚它可能是什么。任何帮助将不胜感激!

Yes, I drop cmd at the end of the loop, and that's ok

不,不是,这就是编译器告诉你的。 Rust 已经完成了它的工作,并阻止您将内存不安全插入到您的程序中。

您在循环内分配一个 String引用它 并从中创建一个 CommandCommand 只说它包含引用,所有引用都在同一生命周期内。然后代码从 Command 中取回这些引用之一,并尝试将其存储在 HashMap 中。

循环退出后,HashMap 将包含对现在释放的 String 的引用,这将是一件非常糟糕的事情。

Anything related to cmd happens inside the loop

不,不是。您将对 String 的引用传递给一个函数。到那时,所有的赌注都落空了。该函数可以签名允许的任何,包括:

fn parse_command<'a>(line: &'a String) -> Result<Command<'a>, &'a str> {
    Ok(Command::Add {
        name: line,
        unit: line,
    })
}

您的代码等同于:

use std::collections::HashSet;

fn main() {
    let mut units = HashSet::new();

    {
        let cmd = String::new();
        units.insert(&cmd);
    }

    println!("{:?}", units);
}