Rust 仅在拆分为多个语句时才编译方法链

Abd*_*awy 2 compiler-errors compilation function method-chaining rust

当我遇到此错误时,我正在解析文件中的一些字符串输入。通常,如果将一系列方法链接在一行上或将它们分成多个操作,则不会产生任何影响。然而,当方法链在一行中时,它不会编译。

当像这样分割成多个语句时,我不会收到错误(链接到游乐场)

let input = std::fs::read_to_string("tst_input.txt").expect("Failed to read input");
let input = input
    .lines()
    .map(|l| {
        let mut iter = l.split(" | ");
        (
            iter.next()
                .unwrap()
                .split_whitespace()
                .collect::<Vec<&str>>(),
            iter.next()
                .unwrap()
                .split_whitespace()
                .collect::<Vec<&str>>(),
        )
    })
    .collect::<Vec<_>>();
Run Code Online (Sandbox Code Playgroud)

当它出现在像这样的单个语句中时,我会遇到终身错误(链接到游乐场)

let input = std::fs::read_to_string("tst_input.txt")
    .expect("Failed to read input")
    .lines()
    .map(|l| {
        let mut iter = l.split(" | ");
        (
            iter.next()
                .unwrap()
                .split_whitespace()
                .collect::<Vec<&str>>(),
            iter.next()
                .unwrap()
                .split_whitespace()
                .collect::<Vec<&str>>(),
        )
    })
    .collect::<Vec<_>>()
Run Code Online (Sandbox Code Playgroud)
error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:2:17
   |
2  |       let input = std::fs::read_to_string("tst_input.txt")
   |  _________________^
3  | |         .expect("Failed to read input")
   | |_______________________________________^ creates a temporary which is freed while still in use
...
18 |           .collect::<Vec<_>>();
   |                               - temporary value is freed at the end of this statement
19 |       println!("{:?}", input);
   |                        ----- borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value
Run Code Online (Sandbox Code Playgroud)

这两种情况应该实际上相同吗?为什么编译器以不同的方式对待它们?这可能是编译器错误吗?

Cer*_*rus 6

这两种情况并不相同,因为存储的信息不同。

在 Rust 中,变量具有语义意义:它们充当存储信息的地方,更重要的是,它们定义了此信息何时被销毁 - 这是由DropTrait处理的。默认情况下,drop每个超出范围的变量都会调用方法;这可以被mem::forget和其他一些函数覆盖,例如Box::into_raw,但这些都是相当小众的情况。

在第一种情况下,正在读取的数据存储到input类型的变量中String。该类型封装Vec<u8>了,它实现了Drop,因此当input超出范围时该数据将被释放。然后,第二个input变量是类型Vec<(Vec<&str>, Vec<&str>)>- 您可以看到它包含一个引用,因此它是从第一个变量借用的input,因此它的生存时间必须不再是源字符串。在这里,这是满足的 - 当然,只要您不尝试在堆栈中返回该值,在这种情况下源字符串将被删除,并且引用将悬空。

然而,在单行版本中,字符串不存储在任何地方 - 它是一个临时的,在语句末尾被销毁。这就是为什么你不被允许保留任何对它的引用。但是,您可以通过插入额外的映射操作来制作分割数据的自有版本:

let _: Vec<(Vec<String>, Vec<String>)> = std::fs::read_to_string("tst_input.txt")
    .expect("Failed to read input")
    // This iterator borrows from the temporary...
    .lines()
    .map(|l| {
        // ...this iterator reborrows that borrow...
        let mut iter = l.split(" | ");
        (
            iter.next()
                .unwrap()
                .split_whitespace()
                // ...and this operation clones the source data,
                // so they are copied to the new owned location,
                // and not referenced anymore, so can be freely dropped
                .map(str::to_owned)
                .collect::<Vec<_>>(),
            iter.next()
                .unwrap()
                .split_whitespace()
                .map(str::to_owned)
                .collect::<Vec<_>>(),
        )
    })
    .collect::<Vec<_>>();
Run Code Online (Sandbox Code Playgroud)