`mem::drop` for non-`Copy` types/generics

Hello,

I don't seek help in the sense that I am unable to make something compile, I just want to understand things better. Sadly I cannot replicate it on a playground code, it almost seems like it has to do with the borrow checker getting to it's limits the more nested generics get.

In a method of a generic type I want to call another &mut self method at the end. But I can't do that because of this error:

error[E0502]: cannot borrow `self.log_manager` as mutable because it is also borrowed as immutable
   --> src\reversible_system\state_manager.rs:683:9
    |
643 |         let current_entry = self.log_manager.try_entry().un...
    |                             ---------------------------- immutable borrow occurs here
...
683 |         self.log_manager.next().unwrap_or_panic(globals, &s...
    |         ^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs 
here
684 |     }
    |     - immutable borrow might be used here, when `current_state` is dropped and runs the destructor for type `<<T as ReversibleSystem>::State as StateOption>::StateFromLog<'_, <T as ReversibleSystem>::Chunk>`

The mentioned generic type at the end has basically no bounds, so it is not bounded to be Copy.
I borrowed something from &self earlier as the error noted, here:

    let current_entry = self.log_manager.try_entry().unwrap_or_panic(globals, &self);
    let current_state = get_state(&current_entry.state_logged, states);

The code only compiles if I add drop(current_state) before the last &mut self call at the end:

    drop(current_state);
    self.log_manager.next().unwrap_or_panic(globals, &self); //this compiles now
}

It also works if I bound the generic type from the error message to implement Copy, but I don't want it to be Copy.

Now I am wondering, why does the compiler not drop the type where it can to make it runnable? Is it because I have to be explicit about the drop order? Is it confused from my generics? Something else?

I also have at some point to put logic in its own scope (brackets) as a partial move from a type happens there and I cannot drop an incomplete type that would also run it's destructor at the method end.

I also would love if an explaination came with a code example why things happen the way they do.

The error is because current_state holds a reference to self, which it might access when the destructor of current_entry runs. You're not allowed to use self mutably in the middle between two such accesses.

When the type is copy, then current_entry doesn't have a destructor, and so the issue goes away.

Lifetimes have no influence on when destructors run. Unless you explicitly drop something, destructors always run at the end of the current scope in reverse declaration order.

It's not that the destructor is moved to an earlier point when the type is Copy. Rather, there is no destructor at all.

3 Likes

Okay, but how comes it is so hard to replicate this error? When the destructor is ran seemed to be up to the compiler so far for me, order aside.

If I take for example this:

struct LogManager;
struct CurrentEntry<'a>(&'a LogManager);
struct CurrentState<'a, 'b: 'a>(&'a CurrentEntry<'b>);
fn mutate<T>(_: &mut T){}

fn main() {
    let mut log_manager = LogManager;
    let current_entry = CurrentEntry(&log_manager);
    let current_state = CurrentState(&current_entry);
    mutate(&mut log_manager);
    //drop(current_state); drop(current_entry); drop(log_manager);
}

In the comment I do what you described, what the compiler does, dropping the variables in reverse order at the end of the scope. But I am still able to mutate log_manager despite it being borrowed as immutable for the following destruction. The other two variables must be dropped earlier because if I uncomment the drops, it fails to compile.

If that is true, then when does the compiler decide to not take that liberty and requires me to drop things before the scope's end?

It's because CurrentEntry and CurrentState don't have destructors. Try adding this:

impl<'a, 'b: 'a> Drop for CurrentState<'a, 'b> {
    fn drop(&mut self) {}
}

You will get an error:

error[E0502]: cannot borrow `log_manager` as mutable because it is also borrowed as immutable
  --> src/main.rs:15:12
   |
13 |     let current_entry = CurrentEntry(&log_manager);
   |                                      ------------ immutable borrow occurs here
14 |     let current_state = CurrentState(&current_entry);
15 |     mutate(&mut log_manager);
   |            ^^^^^^^^^^^^^^^^ mutable borrow occurs here
16 |     //drop(current_state); drop(current_entry); drop(log_manager);
17 | }
   | - immutable borrow might be used here, when `current_state` is dropped and runs the `Drop` code for type `CurrentState`
1 Like

Ooh I thought it is a binary thing: Copy and !Copy.

Can I bound types to not implement Drop? It would make my methods much more readable.

Nope.

In fact, even if you could, it wouldn't be enough. A type can have a destructor even if it doesn't implement Drop. That happens when it has a field with a destructor.

No, it's a different binary thing: destructor or no destructor. It's just that if a type is Copy, then it can't have a destructor.

2 Likes

One last question to wrap it up for me, if I emulate the situation with generics only, the compiler also does not (always) assume these types implement Drop:

struct NonCopyRef<'a, T: ?Sized, U>(&'a T, U);
trait Bar<U>{
    fn mutate(&mut self);
    fn get_non_copy_wrapper<'a>(&'a self) -> NonCopyRef<'a, Self, U>;
}
fn foo<T: Default + Bar<U>, U>(){
    let mut t = T::default();
    let u = t.get_non_copy_wrapper();
    t.mutate();
    //drop(u);
}

This compiles, even though U might have a destructor.
Does the compiler partially destruct NonCopyRef here so the reference no longer exists?

In the case of NonCopyRef, only the second field can have a destructor, so it cannot possibly access the reference when it runs.

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.