为什么编译器告诉我要考虑使用 `let` 绑定,而我已经在使用了?

7
我的错误是什么,如何修复它?
fn get_m() -> Vec<i8> {
    vec![1, 2, 3]
}

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

(playground)

编译器的错误建议“考虑使用let绑定”——但我已经在使用了:

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 代码,以至于我已经掌握了借用检查器……显然我还没有。

这个问题类似于使用`let`绑定来增加值的生命周期,但不涉及将表达式分解成多个语句,所以我认为问题并不完全相同。


3
关于阅读错误信息的提示:“借用值的生命周期不够长”。哪个借用值?看一下跨度,它是 get_m()。 “考虑使用 let 绑定来增加其生命周期”。也就是说,在 get_m() 部分使用 let 绑定。 - Chris Morgan
感谢@ChrisMorgan!波浪线的范围是一个很好的线索。 - Bosh
2个回答

12
问题在于`Peekable`迭代器的生命周期延续到函数的结尾,但它持有一个对`get_m`返回的向量的引用,该引用只在包含该调用的语句的时间内存在。
实际上这里有很多事情要处理,所以我们一步一步来解决:
- `get_m`分配并返回一个类型为`Vec`的向量。 - 我们调用`.iter()`。令人惊讶的是,`Vec`没有`iter`方法,也没有实现任何拥有该方法的特性。因此,这里有三个子步骤: - 任何方法调用都会检查其`self`值是否实现了`Deref`特性,并在必要时应用它。`Vec`确实实现了`Deref`,因此我们隐式调用了它的`deref`方法。然而,`deref`将其`self`参数按引用获取,这意味着`get_m()`现在是出现在左值上下文中的rvalue。在这种情况下,Rust会创建一个临时变量来保存该值,并传递对其的引用。(注意这个临时变量!) - 我们调用`deref`,得到一个类型为`&[i8]`的切片,借用了向量的元素。 - 此切片实现了`SliceExt`特性,该特性具有`iter`方法。终于有了!这个`iter`也将其`self`参数按引用获取,并返回一个持有该切片引用的`std::slice::Iter`。 - 我们调用`.peekable()`,与之前一样,`std::slice::Iter`没有`peekable`方法,但它实现了`Iterator`;对于每个`Iterator`都实现了`IteratorExt`;而`IteratorExt`确实拥有一个`peekable`方法。它按值获取其`self`,因此`Iter`被消耗掉,我们得到一个`std::iter::Peekable`,它也持有一个对切片的引用。 - 然后将这个`Peekable`绑定到变量`vals`上,它的生命周期延续到函数的结尾。 - 现在持有原始`Vec`的临时变量,它的元素对应于`Peekable`,已经死亡。糟糕。这就是所借用的值没有存活足够长的问题。
但是,临时变量之所以会死亡,只是因为这是临时变量的规则。如果我们给它一个名字,那么它就会在其名称在作用域内的时间内存活:
let vec = get_m();
let mut peekable = vec.iter().peekable();
println!("Saw a {:?}", vals.peek());

我想那就是这个故事。然而,仍然让我困惑的是,即使没有名称,为什么这个临时变量不会更长久存在。 Rust 参考文献中写道:“临时变量的生命周期等于指向它的任何引用的最大生命周期。” 但在这里显然不是这样。


非常感谢您抽出时间来深入了解这里!快速问题:我可以去哪里学习像“任何方法调用都会检查其自身值是否实现了Deref特性,并在必要时应用它”这样的东西?我的意思是,语言规范是一个来源;但是否有更高级别的东西? - Bosh
1
我是从阅读 Rust 参考手册中得到的:http://doc.rust-lang.org/reference.html 特别是关于“字段表达式”的部分。但那并不是非常清晰;我听说参考手册很快就要进行重大更新了。另一个来源是 Deref trait 本身的文档:http://doc.rust-lang.org/std/ops/trait.Deref.html 但那也不是很好。我做了很多实验。 - Jim Blandy
我不确定临时生命周期规则的最佳参考是什么,但可以肯定的是,引用中的那句话是不正确的。这篇博客文章涵盖了当前规则制定的许多原因,但我认为它还没有完全更新到实施的规则:http://smallcultfollowing.com/babysteps/blog/2014/01/09/rvalue-lifetimes-in-rust/ 希望我们能尽快更新参考(还要注意,有一个已经被接受但未实现的RFC在某些情况下调整规则,#66)。 - Niko Matsakis

6
这是因为您正在尝试在get_m()内部运行实际向量上的.iter().peekable(),而该向量被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());
}

(示例代码)
结果:
Saw a Some(1)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接