Extend the special‐case move‐out‐of‐deref behavior of Box<T> to
ManuallyDrop<T>. Additionally, allow partial moves out of a T stored inside
ManuallyDrop<T> even when there is a Drop impl for T.
Motivation
Often, instead of dropping a struct, one wants to move out of one of its fields.
However, this is impossible to do in safe code when the struct implements
Drop, requiring use of unsafe APIs like std::ptr::read, or
runtime‐expensive workarounds like wrapping the field in an Option and using
take().
;
;
I ran into this limitation while working on the async-lock library.
It has also been discussed elsewhere:
- Blog post by @withoutboats
- Pre‐RFC from 2024
- Pre‐RFC from 2019.
- Blog post from 2018 (recommends
mem::uninitializedas a workaround!) - RFC #1180
- Internals thread from 2014
Explanation
In today’s Rust, Box<T> has the unique capability that it is possible to move
out of a dereference of it.
let b: = Boxnew;
let s: String = *b; // `b`’s backing allocation is dropped here
Partial moves are also permitted.
let s: String;
This RFC extends this capability to ManuallyDrop.
use ManuallyDrop;
let m: = new;
drop; // `m.1` moved out of here
// `m.0` is never dropped
In addition, partial moves out of a ManuallyDrop<T>’s contents are allowed
even when there is a Drop impl for T.
;
let m: = new;
let s: String = m.1; // `m.1` moved out of here
// `m.0` is never dropped, and nothing is printed.
The example from the motivation section would be rewritten as:
Drawbacks
- Adds more “magic” to the language.
- This change would give safe Rust code a new capability (moving out of fields
of
Drop-implementing structs). It’s currently possible for the soundess ofunsafecode to rely on this capability not existing (though any such API is also unsound if combined withreplace_with). For example:
use unreachable_unchecked;
/// If the bomb is dropped while armed,
/// it explodes and triggers undefined behavior.
/// Disarms the bomb before dropping it.
Rationale and alternatives
Versus DerefMove
A more general mechanism for move‐out‐of‐deref, which would subsume Box’s
special‐case support, has long been desired. There have been three
different RFCs
attempting it, and extensive discussion going back to 2014. However, these
proposals have all gone nowhere; finding a good design for this API seems to be
a hard problem. Also, such an API would not subsume this RFC, as partial moves
out of structs with Drop impls would still need hard‐coded compiler support.
In light of these facts, I think adding an existing lang‐item type to an
existing special case is justified while we wait for a more general DerefMove.
Versus a different API
It’s possible that that partial moves out of Drop types scould be supported
via a different API, such as an attribute, macro, or even silently omitting the
Drop call (possibly with a lint). However, the design presented by this RFC
hase several desirable properties:
- Familiarity: move‐out‐of‐deref is already familiar to Rust developers who
have worked with
Box, andManuallyDropis the recommended API for avoiding drop. So, combining these behaviors should be intuitive to users. - Explicitness: There is a prominent indication in the source code
(
ManuallyDrop) that adropimpl is being skipped. - Soundness: with
ManuallyDrop, fields that are not moved out of will be leaked. This includes fields that are inaccessible due to privacy. Therefore, if a type temporarily violates the safety invariant of a private field, relying on itsDropimpl to restore the invariant before the field is dropped, this RFC will preserve the soundness of that API.- APIs that expose broken safety invariants in public fields will become
unsound, as explained above. However, these are already
incompatible with
replace_with. - The tradeoff for preserving soundness is that it is possible to accidentally
leak memory by forgetting to move out of a field. One strategy programmers
can use to mitigate this is to make the
Drop‐impementing type a newtype struct with a single field.
- APIs that expose broken safety invariants in public fields will become
unsound, as explained above. However, these are already
incompatible with
- Interaction with
Copy: the presence or absence of aCopyimplementation can determine whether a particular segment of code performs a copy or a move. Under a model where theDrop::drop()call is simply omitted following a partial move, whetherdrop()is called can therefore depend on the presence of aCopyimpl—which could be added in a semver‐compatible dependency upgrade. The design proposed by this RFC avoids this pitfall.
Prior art
This RFC addresses a problem unique to Rust’s move and destructor semantics, so there is no analogue in other languages.
Unresolved questions
None, as far as I am aware.
Future possibilities
A more general DerefMove mechanism is the natural next step, though it would
not subsume this RFC, as explained in the Rationale
section.