为啥“临时是块末尾表达式的一部分”是一个错误?

Posted

技术标签:

【中文标题】为啥“临时是块末尾表达式的一部分”是一个错误?【英文标题】:Why is "the temporary is part of an expression at the end of a block" an error?为什么“临时是块末尾表达式的一部分”是一个错误? 【发布时间】:2021-05-04 10:00:49 【问题描述】:

这可能是我不了解借阅检查器的一些技术细节的教科书案例,但如果有人能为我解决这个问题会很好。

我有这段(非常简化的)代码,编译得非常好。

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() 的生命周期有什么不同,这使得一个工作和一个失败?

【问题讨论】:

I further simplified and debugged your example here。这真的很奇怪,我认为这可能是一个编译器错误。 @pretzelhammer 并且可以在std 中轻松修复,而无需接触编译器。 play.rust-lang.org/… 虽然这很有趣,但它根本不需要额外的绑定。 @PeterHall 现在我很困惑。为什么 that 修复它? o.O @PeterHall 在我看来,编译器错误是我希望函数返回位置中使用的impl Iterator + '_impl Iterator&lt;Item = &amp;i32&gt; 都被解析为具体类型std::slice::Iter&lt;'_, i32&gt; 和在所有场景中的行为与具体类型相同,无需任何额外的技巧或变通方法。 【参考方案1】:

这与Why do I get "does not live long enough" in a return value? 具有基本相同的答案,并且它在错误本身中有所解释,但我会详细说明。这种行为与普通的块表达式相同:

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
   |         ^^^^^^^                                     ^^^

scope of temporary values 通常是创建它们的语句。在上面的代码中,example 是一个变量,它在块的末尾被销毁。但是,example.iter() 创建了一个临时的impl Iterator,其临时范围是完整的let foo = ... 语句。所以评估时的步骤是:

评估example.iter().all(...)的结果 放下example 将结果分配给foo 放下impl Iterator

您可能会看到哪里会出错。引入变量的原因是因为它迫使任何临时对象更快地被删除。讲函数的时候情况略有不同,但效果是一样的:

在函数体的最终表达式中创建的临时变量在函数体中绑定的任何命名变量之后被删除,因为没有更小的封闭临时范围。

关于cmets:

impl Iterator 替换为 std::slice::Iter&lt;'_, i32&gt;(在 pretzelhammer 的示例中)时它起作用的原因是因为 drop checker 知道 slice::Iter 在 drop 时不会访问 example 而它必须假设impl Iterator 确实如此。

它与fn my_all(mut self, ...) 一起工作的原因(在Peter Hall 的示例中)是因为all 通过引用获取迭代器,但my_all 通过值获取它。临时的impl Iterator 在表达式结束前被消耗和销毁。

通过查看与此相关的各种 Rust 问题,很明显有些人会认为这是一个错误。 ...; EXPR ...; let x = EXPR; x 可能不同,这绝对不明显。但是,由于添加了诊断和文档来加强和解释这种行为,我不得不假设这些临时范围规则允许更合理的代码。

【讨论】:

以上是关于为啥“临时是块末尾表达式的一部分”是一个错误?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我收到“表达式不可分配”错误?

为啥这个表达式会导致浮点错误?

为啥`return a or b`是Ruby中的空值表达式错误?

语法错误,插入“AssignmentOperator 表达式”以完成表达式。为啥会这样? [复制]

C# 中的错误:“表达式树可能不包含基本访问” - 为啥不呢?

为啥在 VC++ 调试器上计算表达式时会出现“成员函数不存在”错误?