为什么编译器告诉我考虑使用 `let` 绑定?

Why does the compiler tell me to consider using a `let` binding" when I already am?

我的错误是什么以及如何解决?

fn get_m() -> Vec<i8> {
    vec![1, 2, 3]
}

fn main() {
    let mut vals = get_m().iter().peekable();
    println!("Saw a {:?}", vals.peek());
}

(playground)

编译器的错误提示 "consider using a let binding" — 但我已经是:

error[E0597]: borrowed value does not live long enough
 --> src/main.rs:6:45
  |
6 |     let mut vals = get_m().iter().peekable();
  |                    -------                  ^ temporary value dropped here while still borrowed
  |                    |
  |                    temporary value created here
7 |     println!("Saw a {:?}", vals.peek());
8 | }
  | - temporary value needs to live until here
  |
  = note: consider using a `let` binding to increase its lifetime

这显然是一个新手问题——虽然我认为此时我已经写了足够多的 Rust,我已经掌握了借用检查器……显然我还没有。

此题与Using a `let` binding to increase value lifetime类似,但不涉及将表达式分解为多个语句,所以我认为问题不相同。

发生这种情况是因为您正试图 运行 您的 .iter().peekable()get_m() 内部的实际向量上,它被 vals 重新引用。

基本上,您想要这样的东西:

fn get_m() -> Vec<i8> {
    vec![1, 2, 3]
}

fn main() {
    let vals = get_m();
    let mut val = vals.iter().peekable();
    println!("Saw a {:?}", val.peek());
}

(Playground)

结果:

Saw a Some(1)

问题是 Peekable 迭代器一直存在到函数的末尾,但它持有对由 get_m 编辑的向量 return 的引用,它只持续那么久作为包含该调用的语句。

这里其实有很多事情要做,我们一步步来吧:

  • get_m 分配并 return 一个向量,类型为 Vec<i8>
  • 我们打电话 .iter()。令人惊讶的是,Vec<i8> 没有 iter 方法,它也没有实现任何具有该方法的特征。所以这里分为三个子步骤:
    • 任何方法调用都会检查其 self 值是否实现了 Deref 特性,并在必要时应用它。 Vec<i8> 确实实现了 Deref,所以我们隐式调用它的 deref 方法。但是,deref 通过引用获取其 self 参数,这意味着 get_m() 现在是出现在左值上下文中的右值。在这种情况下,Rust 会创建一个临时对象来保存该值,并将引用传递给它。 (暂时关注一下!)
    • 我们调用 deref,产生一个 &[i8] 类型的切片借用向量的元素。
    • 此切片实现了 SliceExt 特性, 具有 iter 方法。最后!此 iter 还通过引用获取其 self 参数,并且 return 是一个 std::slice::Iter 保存对切片的引用。
  • 我们打电话 .peekable()。和以前一样,std::slice::Iter 没有 peekable 方法,但它确实实现了 IteratorIteratorExt 为每个 Iterator 实施;并且 IteratorExt peekable 方法。这将其 self 按值获取,因此 Iter 被消耗,我们在 return 中得到一个 std::iter::Peekable,再次持有对切片的引用。
  • Peekable 然后绑定到变量 vals,该变量一直存在到函数的末尾。
  • Peekable 引用其元素的原始 Vec<i8> 的临时保存者现已死亡。哎呀。这是借来的价值不够长寿。

但是临时工死在那里只是因为这是临时工的规则。如果我们给它一个名字,那么只要它的名字在范围内,它就会持续存在:

let vec = get_m();
let mut peekable = vec.iter().peekable();
println!("Saw a {:?}", vals.peek());

我想这就是故事。然而,仍然让我感到困惑的是,为什么即使没有名字,临时工也不会活得更久。 Rust 参考资料说,"A temporary's lifetime equals the largest lifetime of any reference that points to it." 但这里显然不是这种情况。