Rust中方法链中的临时变量生命周期

8
我正在尝试通过将Rust的生命周期规则与我更熟悉的C++中的类似概念进行比较来学习它。大多数情况下,我的直觉非常好,我可以理解这个规则。然而,在以下情况下,我不确定我的理解是否正确。

在Rust中,临时值的生命周期是其语句的结尾,除非最后一个临时值使用let绑定到名称上。

struct A(u8);
struct B(u8);

impl A {
    fn get_b(&mut self) -> Option<B> {
        Some(B(self.0))
    }
}

fn a(v: u8) -> A {
    A(v)
}

// temporary A's lifetime is the end of the statement
// temporary B binds to a name so lives until the enclosing block
let b = a(1).get_b();

// temporary A's lifetime is the end of the statement
// temporary B's lifetime extends to the enclosing block,
// so that taking reference of temporary works similar as above
let b = &a(2).get_b();

如果临时值在一个if条件语句中,根据参考文献,其生命周期将被限制为条件表达式。
// Both temporary A and temporary B drops before printing some
if a(3).get_b().unwrap().val <= 3 {
    println!("some");
}

现在来谈谈问题:

如果在if条件中使用let,由于模式匹配,我们绑定到临时值的内部部分。我期望由let绑定的临时值将被扩展到封闭块,而其他临时值仍应具有由if条件限制的生命周期。

(在这种情况下,实际上每个东西都被复制了,即使临时的B也可以被删除,但这是一个单独的问题。)

然而,两个临时变量的生命周期都被扩展到封闭的if块。

// Both temporary A and temporary B's lifetime are extended to the end of the enclosing block,
// which is the if statement
if let Some(B(v @ 0...4)) = a(4).get_b() {
    println!("some {}", v);
}

这是否应该被认为是Rust中的一种不一致性?或者我有误解,存在一个一致的规则可以解释这种行为?

完整的代码示例:

请注意,Rust的输出为

some 4
Drop B 4
Drop A 4

而来自 C++ 的输出为

Drop A 4                                                                                                                                                                         
some 4                                                                                                                                                                           
Drop B 4

我阅读了这篇 Reddit thread 和 Rust issue,认为它们非常相关,但我仍然找不到适用于所有 Rust 情况的明确的生命周期规则。

更新:

我不清楚的是为什么关于if条件表达式的临时生命周期规则不适用于if let。 我认为let Some(B(v @ 0...4)) = a(4).get_b()应该是条件表达式,因此临时A的生命周期应受其限制,而不是整个if语句。
将临时B的生命周期延长到整个if语句的行为是可以预期的,因为它被模式匹配所借用。

2
你借用了 &mut self,因此我期望这个结果,尝试通过消费 self => get_b(self) - Stargateur
@Stargateur get_b 确实借用了 &mut self,但返回值不包含生命周期,因此它不需要“保持借用”。我认为这里发生的情况是编译器将整个 if let 表达式视为“当前语句”(即,它与类似的 C++ 代码的输出不同:let tmp = a(4).get_b(); if let Some(B(_)) = tmp { ... })。 - trent
@trentcl 这个问题的解释在这里,https://github.com/rust-lang/rust/issues/37612#issuecomment-304626464,模式匹配会将借用扩展到模式匹配的范围。`let b = a(4).get_b(); if let Some(B(v@ 0...4)) = b { println!("some {}", v); }`应该按预期工作。 - Stargateur
1
不是 borrow 被延长了,而是即使它没有被借用,临时对象的生命周期也被延长了。至少这是我的理解。 - trent
@Stargateur 如果 get_b 消耗了 self,那就是另一种情况。临时变量 A 将在 get_b 函数体内被丢弃,因为它被移动到那里。这不同于在语句末尾丢弃的效果。 - Aetf
@Stargateur 我知道这个问题。我明白模式匹配会延长借用。但是,正如 @trentcl 所说,这里的借用是 Option<B>。临时的 A 不应该被延长,因为它没有被借用。 - Aetf
1个回答

7
一个if let结构只是一个match结构的语法糖。let Some(B(v @ 0...4)) = a(4).get_b()不是在常规if表达式中使用的条件,因为它不是一个计算结果为bool的表达式。根据你的例子:
if let Some(B(v @ 0...4)) = a(4).get_b() {
    println!("some {}", v);
}

它的行为与下面的示例完全相同。没有例外。if let在类型或借用检查器运行之前就被重写成match
match a(4).get_b() {
    Some(B(v @ 0...4)) => {
        println!("some {}", v);
    }
    _ => {}
}

临时变量在匹配块中存在的时间和它们在某些情况下非常有用。比如,如果您的最后一个函数是fn get_b(&mut self) -> Option<&B>,如果临时变量没有在整个匹配块中存在,则它不会通过borrowck。

条件语句不遵循同样的规则,因为在条件语句中的最后一个函数调用不可能持有对任何内容的引用,它必须计算出一个普通的bool值。

参见:


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