使用match
可以确保表达式$left
和$right
只被评估一次,并且在它们的评估过程中创建的任何临时对象至少存在与绑定结果left
和right
相同的时间。
如果展开在比较时和插入到错误消息中再次使用$left
和$right
,则会出现意外行为,如果任一表达式具有副作用。但是,为什么展开不能像这样做:let left = &$left; let right = &$right;
?
考虑:
let vals = vec![1, 2, 3, 4].into_iter();
assert_eq!(vals.collect::<Vec<_>>().as_slice(), [1, 2, 3, 4]);
假设这个扩展到:
let left = &vals.collect::<Vec<_>>().as_slice();
let right = &[1,2,3,4];
if !(*left == *right) {
panic!("...");
}
Rust中,语句内产生的临时值的生命周期通常仅限于该语句本身。因此,该扩展是一个错误:
error[E0597]: borrowed value does not live long enough
--> src/main.rs:5:21
|
5 | let left = &vals.collect::<Vec<_>>().as_slice();
| ^^^^^^^^^^^^^^^^^^^^^^^^ - temporary value dropped here while still borrowed
| |
| temporary value does not live long enough
临时 vals.collect::<Vec<_>>()
的生命周期至少应与left
相同,但实际上它在let
语句结束时就被丢弃了。
与此相反的是扩展:
match (&vals.collect::<Vec<_>>().as_slice(), &[1,2,3,4]) {
(left, right) => {
if !(*left == *right) {
panic!("...");
}
}
}
这会产生相同的临时值,但它的生命周期延伸到整个匹配表达式 - 足够长,让我们比较left
和right
,并在比较失败时将它们插入到错误消息中进行插值处理。
从这个意义上讲,match
是 Rust 的let ... in
构造。
请注意,在非词法生命周期(non-lexical lifetimes)下,这种情况没有改变。尽管它的名称为 NLL,但它不会改变任何值的生命周期 - 即它们何时被丢弃。它只使得借用的作用域更加精确。因此,它在这种情况下不能帮助我们。