Explicit vs. Implicit drop & borrowing

Hello,

When I comment the Drop trait implementation for the struct X, this code compiles.

fn main() {
    
    #[derive(Debug)]
    struct X<'a>(&'a i32);
    
    // impl Drop for X<'_> {
    //     fn drop(&mut self) {
    //         println!("Dropping X now...")
    //     }
    // }
    
    let mut data = vec![1, 2, 3];
    let x = X(&data[0]);
    println!("{:?}", x);
    data.push(4);
    
}

However, when I uncomment it, it does not compile, and it throws the below error. I don't understand why this behavior occurs in this case, because as far as I know, in the first case (case above), the struct is also dropped, right? And yet, it compiles.

  Compiling playground v0.0.1 (/playground)
error[E0502]: cannot borrow `data` as mutable because it is also borrowed as immutable
  --> src/main.rs:15:5
   |
13 |     let x = X(&data[0]);
   |                ---- immutable borrow occurs here
14 |     println!("{:?}", x);
15 |     data.push(4);
   |     ^^^^^^^^^^^^ mutable borrow occurs here
16 |     
17 | }
   | - immutable borrow might be used here, when `x` is dropped and runs the `Drop` code for type `X`

For more information about this error, try `rustc --explain E0502`.
error: could not compile `playground` due to previous error

Basically, these are my questions:

  1. What is the difference between the implicit drop and the explicit drop of a struct / non-primitive value in Rust?
  2. If all values (in this case, a struct) get dropped then why in the first case, the implicit drop is not considered as the last use of the borrow?

P.S: this example is taken from the Rustonomicon

If a type has no destructor, then the compiler does not consider it a use when it goes out of scope.

So, if I don't explicitely declare a destructor for a struct, does that mean that it won't be destroyed by Rust?

I guess you could answer yes to that, but you have to remember that there's nothing to destroy, so the question doesn't make that much sense in the first place.

I ask the question because I thought that safe Rust would clean memory and won't leave any memory leaks or any dangling pointers.
By the way, I meant for a struct in general (it could have fields that point to vectors and strings in the heap, etc). So, correct me if I'm wrong, what you're saying is that Rust won't destroy that struct unless I explicitely implement the Drop trait for it? Isn't that a memory leak?

There are no memory leaks here. Cleaning up the memory containing the struct is a separate thing from running the destructor, and happens after the destructor (if any).

Note that it's important to point out the difference between the memory containing the struct and memory owned by the struct. If you have a Vec<T>, then the vector itself is 24 bytes large, but it also owns a separate allocation on the heap elsewhere. The destructor of Vec<T> will first call the destructor of every element in the vector, and then it will deallocate the heap allocation that it owns. Note here how the destructor of the items in the vector is a separate thing from and runs before deallocating the memory. Note also that the 24 bytes containing the Vec<T> is not destroyed by the destructor of the vector - that's not the job of the vector.

As for structs containing a vector or string, such a struct automatically gets a destructor that cleans up all of its fields.

2 Likes

That's clearer now. Do you know where I could learn about these subtleties? (and other Rust features in a very concise way)

Thank you

The nomicon talks about many different subtleties, though I don't think it discusses this particular one.

1 Like

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.