Moving out of a type implementing Drop

I'm trying to implement a type-safe interface to a native library and encountered an interesting problem.

The main idea is that each "state" the library is in will contain a &'lib mut Library to ensure the associated functions can only be called when the library is in the appropriate state, and that nothing else can use the library at the time.

For correctness reasons I need to call a function when entering a state (usually done in the constructor) and another when the state gets destroyed. When transitioning between two states we also need to pass the &mut Library reference around, however the naive way of doing this doesn't compile.

error[E0713]: borrow may still be in use when destructor runs
##[error]   --> src/lib.rs:142:23
    |
129 |   impl<'lib> RecipeBuilder<'lib> {
    |        ---- lifetime `'lib` defined here
...
141 | /         Recipe {
142 | |             _library: self._library,
    | |                       ^^^^^^^^^^^^^
143 | |         }
    | |_________- returning this value requires that `*self._library` is borrowed for `'lib`
144 |       }
    |       - here, drop of `self` needs exclusive access to `*self._library`, because the type `RecipeBuilder<'_>` implements the `Drop` trait

(see the CI run for more)

There are a couple places where I trigger this error and while I understand exactly why this error occurs, I can't think of a way to tell the compiler it's okay to move self._library because it doesn't get used in the destructor. The nightly #[may_dangle] attribute also doesn't do anything.

Any ideas? I'd prefer to avoid wrapping the _library reference in an Option for ergonomics, and likewise mem::swap()ing in an uninitialized &mut Library (while also being UB) feels like a bit too hacky for my liking.

This is also part of a blog post so if possible I'd prefer to avoid hacky solutions which might teach people bad habits...

3 Likes

Unless you can move in a tombstone version of it, why not just wrap it in an Option, which is the standard way to wrap something that may or may not exist? :slight_smile:

What if you implement Drop not for your whole struct, but for a separate type that you store in a field within the struct? Playground example.

3 Likes

FWIW, ManuallyDrop has ManuallyDrop::take stable in 1.42, which allows you to take the value out without providing a replacement value. And it derefs to the value it wraps, so until the point of calling ManuallyDrop::take in the destructor you can use it as-if it were the wrapped value.

2 Likes

Related: What is the "ideal" way to destruct a guard type? - #9 by Yandros

Repro example:

struct Foo<'lt> (
    &'lt mut (),
);

impl Drop for Foo<'_> {
    fn drop (self: &'_ mut Self)
    {}
}

impl<'lt> Foo<'lt> {
    fn finish (self: Foo<'lt>) -> &'lt mut ()
    {
        self.0
    }
}

Solution starting as of 1.42.0: using ManuallyDrop with its ::take() associated function, designed precisely for this:

use ::core::mem::ManuallyDrop;

pub
struct Foo<'lt> (
    ManuallyDrop<&'lt mut ()>,
);

impl Drop for Foo<'_> {
    fn drop (self: &'_ mut Self)
    {}
}

impl<'lt> Foo<'lt> {
    pub
    fn finish (mut self: Foo<'lt>) -> &'lt mut ()
    {
        unsafe {
            // Safety: Foo Drop impl does not use this field.
            ManuallyDrop::take(&mut self.0)
        }
    }
}

And if you want to be compatible with older versions of Rust, the implementation of ManuallyDrop::take() is just ptr::read():

#![allow(unstable_name_collisions)]

trait CompatHack {
    type T;
    unsafe fn take (this: &'_ mut Self) -> Self::T ;
}

impl<T> CompatHack for ManuallyDrop<T> {
    type T = T;
    unsafe fn take (this: &'_ mut ManuallyDrop<T>) -> T
    {
        ::core::ptr::read(&**this)
    }
}
4 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.