Drop of Option vs Drop of others

Im trying to understand why borrow checker complains in one instance while it doesnt in another.

fn main() {
    let mut v1 = vec![1,2,3];
    
    for i in 0..3 {
        let mut v2 = v1.drain(..);
        println!("{:?}", v2);
        v1= vec![1,2,3];
    }
    

    println!("Done!");
}

In above piece of code I get

error[E0506]: cannot assign to `v1` because it is borrowed
 --> src/main.rs:8:9
  |
6 |         let mut v2 = v1.drain(..);
  |                      -- borrow of `v1` occurs here
7 |         println!("{:?}", v2);
8 |         v1= vec![1,2,3];
  |         ^^ assignment to borrowed `v1` occurs here
9 |     }
  |     - borrow might be used here, when `v2` is dropped and runs the `Drop` code for type `std::vec::Drain`

My thinking: Since v2 is dropped at end of for loop, it might require v1 that it borrowed to do something before it gets dropped. The last line of error is pretty clear on that. Hence, this is not allowed.

But below runs fine

fn main() {
    let mut v1 = Some(vec![1,2,3]);
    
    for i in 0..3 {
        if v1.is_some() {
            let v1_take = v1.take();
            let mut v2 = v1_take.unwrap();
            println!("{:?}", v2);
            v1 = Some(vec![1,2,3]);
        }
    }
    

    println!("Done!");
}

Now take() also borrows v1 mutably just like drain() did for previous example. So why dont I get similar error? Does compiler think "v1 can reassigned because it has None anyways"?

Option::take does borrow the option mutably, but its return value does not contain any reference to the original option; it returns an owned value. So from the borrow checker's point of view, v1 and v2 are completely unrelated and can be accessed independent of one another.

Vec::drain's return value does contain a reference to the original vec, and thus v1 cannot be accessed before the return value, assigned to v2, is dropped. If you explicitly drop v2 before accessing v1, the code works:

fn main() {
    let mut v1 = vec![1, 2, 3];

    for i in 0..3 {
        let mut v2 = v1.drain(..);
        println!("{:?}", v2);
        drop(v2);
        v1 = vec![1, 2, 3];
    }

    println!("Done!");
}
3 Likes

Aah yes. I re-read the doc for drain(). drain() returns an iterator when dropped removes the elements from vec. So it needs original vec to do that.
Your answer made me look for the doc.

A useful thing here is that you can tell the difference just from looking at the method signatures, without knowing anything about what the methods do:

impl<T> Option<T> {
    pub fn take(&mut self) -> Option<T>
}

vs.

impl<T> Vec<T> {
    pub fn drain(&mut self, ...) -> Drain<'_, T>
}

You can see that the return value of Option::take does not borrow from self (no lifetime parameter), while the return value of Vec::drain does, because the elided lifetime parameter '_ is tied to the borrow &mut self. This is exactly the kind of analysis the compiler does when checking your program.

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.