Peter Hall
Peter Hall

Reputation: 58805

Vector doesn't live long enough until I clone it

I don't understand why v doesn't live long enough in snippet (2), but it works in the other cases. If I don't need to clone the variable in (1), why do I need to clone it to get the second case to work?

fn main() {
    // (1)
    let v = make_vec().unwrap();
    let m = v.last();

    // (2) v doesn't live long enough!
    let m = make_vec()
        .and_then(|v| v.last());

    // (3) Fixed!
    let m = make_vec()
        .and_then(|v| v.last().cloned());
}

fn make_vec() -> Option<Vec<u32>> {
    Some(vec![1, 2, 3])
}

Upvotes: 1

Views: 271

Answers (1)

Shepmaster
Shepmaster

Reputation: 431599

In the first case, ownership of the Option passes from make_vec to the unwrap call. unwrap consumes the Option and returns the Vec, whose ownership passes to the variable v. The call to last returns a reference into v.

In the second case, ownership of the Option passes from make_vec to the call to and_then. and_then consumes the Option, and passes ownership of the Vec to the closure. The call to last in the closure returns a reference into the Vec. Since the closure owned the vector but is now finished running, the Vec will be dropped. The reference into the Vec would point to memory that is no longer valid, thus the compilation error.

In the third case, ownership of the Option passes from make_vec to the call to and_then. and_then consumes the Option, and passes ownership of the Vec to the closure. The call to last in the closure returns a reference into the Vec. The referred-to item is cloned, which creates a new item that is distinct from the Vec. When the Vec is dropped after the closure, there are no references into it that could cause a problem.

The types of m differ between your cases. The first and second cases would return a Option<&u32> if they both worked. The third case returns a Option<u32>.

There's a fourth option as well:

let r = make_vec();
let m = r.as_ref().and_then(|v| v.last());

This converts the Option<T> into an Option<&T>. This new Option references the original option and can be consumed by the call to and_then.

There's a fifth option as well! ^_^ If you are just going to throw away the vector, you can be a bit more explicit about the fact that you want to take it anyway:

let m = make_vec().and_then(|v| v.pop());

Does clone here actually copy things in memory or will the compiler optimise it to effectively pass the ownership of the vector element back? Since this was u32s throughout, I was expecting that they would generally be copied instead of referenced.

Optimizing is a tricky thing, and the only true answer is to look at the optimized output. I would assume that anything that is Copy and is "small enough" wouldn't really cause a problem. However, I might look at making my code as semantic as possible in order to help the optimizer. I'd probably try to have the pop variant if that code is what you mean.

Upvotes: 4

Related Questions