I'm trying to learn Rust's lifetime rules by comparing it to similar concepts in C++, which I'm more familiar with. Most of the time, my intuition works really well and I can make sense the rule. However, in the following case, I'm not sure if my understanding is correct or not.
In Rust, a temporary value's lifetime is the end of its statement, except when the last temporary value is bound to a name using 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 the temporary value is in an if
condition, according to the reference, the lifetime is instead limited to the conditional expression.
// Both temporary A and temporary B drops before printing some
if a(3).get_b().unwrap().val <= 3 {
println!("some");
}
If putting let
in if
condition, because of pattern matching, we are binding to the inner part of the temporary value. I'd expect the temporary value bound by let
to be extended to the enclosing block, while other temporary values should still have a lifetime limited by the if condition.
(In this case actually everything is copied I would say even temporary B can be dropped, but that's a separate question.)
However, both temporaries' lifetimes are extended to the enclosing if
block.
// 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);
}
Should this be considered an inconsistency in Rust? Or am I misunderstanding and there is a consistent rule that can explain this behavior?
Note the output from Rust is
some 4
Drop B 4
Drop A 4
while the output from C++ is
Drop A 4
some 4
Drop B 4
I have read this Reddit thread and Rust issue, which I think is quite relevant, but I still can't find a clear set of lifetime rule that works for all the cases in Rust.
What I'm unclear about is why the temporary lifetime rule about if
conditional expression does not apply to if let
. I think the let Some(B(v @ 0...4)) = a(4).get_b()
should be the conditional expression, and thus the temporary A's lifetime should be limited by that, rather than the entire if
statement.
The behaviour of extending temporary B's lifetime to the entire if
statement is expected, because that is borrowed by the pattern matching.
An if let
construct is just syntactic sugar for a match
construct. let Some(B(v @ 0...4)) = a(4).get_b()
is not a conditional used in a regular if
expression, because it is not an expression that evaluates to bool
. Given your example:
if let Some(B(v @ 0...4)) = a(4).get_b() {
println!("some {}", v);
}
It will behave exactly the same as the below example. No exceptions. if let
is rewritten into match
before the type or borrow checkers are even run.
match a(4).get_b() {
Some(B(v @ 0...4)) => {
println!("some {}", v);
}
_ => {}
}
Temporaries live as long as they do in match blocks because they sometimes come in handy. Like if your last function was fn get_b(&mut self) -> Option<&B>
, and if the temporary didn't live for the entire match block, then it wouldn't pass borrowck.
If conditionals don't follow the same rule because it's impossible for the last function call in an if conditional to hold a reference to anything. They have to evaluate to a plain bool
.
See:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With