Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rust temporary variable lifetime in method chaining

Tags:

rust

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");
}

Now to the question:

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?

Full code example:

  • playground
  • The same thing implemented in C++ that matches my expectation

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.

Update:

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.

like image 386
Aetf Avatar asked Mar 22 '19 02:03

Aetf


Video Answer


1 Answers

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:

  • Rust issue 37612
like image 63
notriddle Avatar answered Oct 24 '22 08:10

notriddle