How does explicit invocation of `drop` impact the borrow checker?

No it couldn’t just keep around the value in global storage or other threads for longer than its call duration. It only could do that it it had – say – a T: 'static bound, which would then in turn be something the borrow checker would be aware of.

Or if it had a T: 'a bound with some lifetime 'a that appears in a different function argument, it could somehow stash the value into data related to that other parameter. Etc…

Of course it doesn’t have to drop the value, but it has to either drop it or leak it, it becomes inaccessible either way, which is all the borrow checker would need to care about.

3 Likes

There is a drawback in your intepretation

struct Foo<'a>(&'a String);
fn main() {
    let foo;
    let s = String::from("123");
    foo = Foo(&s);
    // panic!();  the compiler assume the panic
    drop(foo); 
}

The destruction order at the panic point is still first s and then foo, which is equivalent to the original example, however, this case can be compiled.

That's because without an explicit Drop impl that had access to the reference, a type such as Foo<'a> is allowed to be (implicitly) dropped even when the contained reference is already dead. In particular, your code will still work without the drop(foo) or even with an explicit drop(s).

There's even special (unstable and unsafe) options that allow certain standard library types with an explicit drop to do the same. E. g. Vec<Foo<'a>> will still work if Foo<'a> does even though the Vec does some custom operations (e. g. memory deallocation) when dropped.

That means if we explicitly implement Drop for a struct type, the compiler will assume we will access all fields of the struct type even if we don't do this. If we don't explicitly implement Drop, the compiler can ensure that we cannot access these fields when the container object is being destroyed. So, whether these fields are killed or alive at the destruction of containing object, they don't matter.

1 Like

Sounds accurate.

The rationale is that the function body of Drop functions should not matter for how the type can or cannot be used, hence the conservative assumption that all fields could potentially be accessed in the Drop impl.

In other words, changing the code in the Drop implementation of your types will never break compilation for users of your type, whereas adding a new Drop implementation can be a breaking change.

1 Like

This function is easy to cause UB. playground

I just learnt that when reading Stacked Borrows: An Aliasing Model For Rust.

That was exactly the reason it was introduced, AFAIK - to turn potential UB into observable one.

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.