【问题标题】:Rust compiles method chain only when split to multiple statementsRust 仅在拆分为多个语句时编译方法链
【发布时间】:2021-12-27 04:30:11
【问题描述】:

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

拆分成多个语句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

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

【问题讨论】:

    标签: function rust compiler-errors compilation method-chaining


    【解决方案1】:

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

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

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

    然而,在单行版本中,字符串不存储在任何地方——它是一个temporary,它在语句结束时被销毁。这就是为什么你不能持有任何对它的引用。但是,您可以通过插入 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<_>>();
    

    【讨论】:

      【解决方案2】:

      对问题进行最小限度的重现可能会有所帮助

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

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

      因为split 返回引用该字符串值的数据,它的生命周期链接到该字符串,所以它也结束了。

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

      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 的引用也是有效的。

      【讨论】:

        猜你喜欢
        • 2014-02-09
        • 2021-03-03
        • 2019-01-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-24
        相关资源
        最近更新 更多