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

Rust compiles method chain only when split to multiple statements

当我遇到这个错误时,我正在解析文件中的一些字符串输入。通常,如果将一系列方法链接在一行上或将它们分成多个操作,应该不会有什么不同。然而在这里,当方法链在一行中时,它不会编译。

拆分为多个语句时我没有收到错误消息like so (link to playground)

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<_>>();

我在单个语句中遇到生命周期错误like so (link to playground)

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<_>>()
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

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

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

在 Rust 中,变量具有语义意义:它们充当存储信息的地方,更重要的是,它们定义了何时销毁此信息——这由 Drop trait. By default, drop method is called for every variable which goes out of scope; this can be overridden with mem::forget and some other functions like Box::into_raw 处理,但是这些是相当小众的案例。

在第一种情况下,正在读取的数据存储在类型为Stringinput变量中。这个类型 wraps Vec<u8>, which implements Drop, so this data is deallocated when input goes out of scope. Then, the second input variable is of type Vec<(Vec<&str>, Vec<&str>)> - 你可以看到它包含一个引用,所以它是从第一个 input 借来的,所以它必须不再是源字符串。在这里,这是令人满意的 - 当然,只要您不尝试 return 这个值在堆栈中,在这种情况下源字符串将被删除,并且引用将悬空。

然而,在单行版本中,字符串没有存储在任何地方——它是一个 temporary, which is destroyed right at the end of statement. That's why you're not allowed to hold any references to it. You can, however, make an owned version of the split data, by inserting an extra mapping operation:

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<_>>();

对问题进行最少的再造可能会有所帮助

let split_value = String::from("example")// <- string owned value
    .split("x");
// string has no owner, so its lifetime ends
println!("{:?}", split_value); //error

引用不能超过它所引用的值的生命周期。由于该字符串未存储在任何地方,因此没有所有者,因此该值的生命周期结束。

并且因为 split returns 引用该字符串值的数据,其生命周期与该字符串相关联,因此它也结束。

通过将结果存储在变量中,字符串现在的生命周期超过表达式。

let str_result = String::from("example"); //str_result owns the string value
let split_value = s.split("x");
println!("{:?}", r);

split_value 可以打印,因为 str_result 的生命周期在函数结束时结束,因此对 str_result 的引用也是有效的。