这可能是我不理解借用检查器的一些技术细节的教科书案例,但如果有人能为我澄清这一点就太好了。
我有这个(非常简化的)代码块,它编译得非常好。
pub struct Example(pub Vec<String>);
impl Example {
pub fn iter(&self) -> impl Iterator<Item=&String> {
self.0.iter()
}
}
pub fn some_condition(_: &str) -> bool {
// This is not important.
return false;
}
pub fn foo() -> bool {
let example = Example(vec!("foo".to_owned(), "bar".to_owned()));
let mut tmp = example.iter();
tmp.all(|x| some_condition(x))
}
pub fn main() {
println!("{}", foo());
}
然而,我尝试的第一件事(在我看来,应该等同于上面的)是完全省略临时变量tmp
,如下所示
pub fn foo() -> bool {
let example = Example(vec!("foo".to_owned(), "bar".to_owned()));
example.iter().all(|x| some_condition(x))
}
但是这个版本会产生以下错误:
error[E0597]: `example` does not live long enough
--> so_temporary.rs:23:3
|
23 | example.iter().all(|x| some_condition(x))
| ^^^^^^^-------
| |
| borrowed value does not live long enough
| a temporary with access to the borrow is created here ...
24 | }
| -
| |
| `example` dropped here while still borrowed
| ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `impl std::iter::Iterator`
|
= note: The temporary is part of an expression at the end of a block. Consider forcing this temporary to be dropped sooner, before the block's local variables are dropped. For example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block.
现在,很明显,错误末尾的注释是一个很好的建议,这就是为什么我引入临时来解决这个问题。但是我不明白为什么可以解决这个问题。我的tmp
变量与直接嵌入表达式的example.iter()
变量的生命周期有什么不同,这使得一个工作,一个失败?
这基本上和为什么我得到"我活得不够长"一样。在返回值中?它在错误本身中得到了某种解释,但我会详细说明。此行为与普通块表达式相同:
pub struct Example(pub Vec<String>);
impl Example {
pub fn iter(&self) -> impl Iterator<Item=&String> {
self.0.iter()
}
}
pub fn main() {
let foo = {
let example = Example(vec!("foo".to_owned(), "".to_owned()));
example.iter().all(String::is_empty)
};
println!("{}", foo);
}
error[E0597]: `example` does not live long enough
--> src/main.rs:12:9
|
12 | example.iter().all(String::is_empty)
| ^^^^^^^-------
| |
| borrowed value does not live long enough
| a temporary with access to the borrow is created here ...
13 | };
| -- ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `impl Iterator`
| |
| `example` dropped here while still borrowed
|
= note: the temporary is part of an expression at the end of a block;
consider forcing this temporary to be dropped sooner, before the block's local variables are dropped
help: for example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block
|
12 | let x = example.iter().all(String::is_empty); x
| ^^^^^^^ ^^^
临时值的作用域通常是创建临时值的语句。在上面的代码中,example
是一个变量,它在代码块的末尾被销毁。然而,example.iter()
创建了一个临时的impl Iterator
,它的临时作用域是完整的let foo = ...
语句。所以求值的步骤是:
- 评估
example.iter().all(...)
的结果drop - 将结果赋给
foo
- drop
impl Iterator
example
您可能会看到这可能出错的地方。引入变量之所以有效,是因为它强制更快地删除任何临时变量。在讨论函数时,情况略有不同,但效果是相同的:
在函数体的最终表达式中创建的临时变量在函数体中绑定的任何命名变量之后删除,因为没有更小的封闭临时作用域。
关于注释:
当
impl Iterator
被std::slice::Iter<'_, i32>
取代时(在pretzelhammer的例子中),它工作的原因是因为drop检查器知道slice::Iter
在drop时不访问example
,而它必须假设impl Iterator
。它与
fn my_all(mut self, ...)
一起工作的原因(在Peter Hall的例子中)是因为all
通过引用获取迭代器,而my_all
通过值获取迭代器。临时impl Iterator
在表达式结束前被消耗和销毁。
从与此相关的各种Rust问题来看,很明显有些人会认为这是一个bug。{ ...; EXPR }
和{ ...; let x = EXPR; x }
的区别并不明显。然而,由于已经添加了诊断和文档来加强和解释这种行为,我不得不假设这些临时范围规则允许更合理的代码。