Move out of deref for `ManuallyDrop`

(This idea was previously mentioned by @whitequark in this pre-RFC, as the final bullet of the "Alternatives" section.)

A common use for ManuallyDrop is for when one wants to get the fields of a struct by value, without calling drop glue for the struct as a whole. However, in current Rust, only Box allows moving out of deref, so one must resort to unsafe tricks like ptr::read to emulate it; for example I had to do this here.

It would be nice if the move-out-of-deref behavior of Box<T> was extended to ManuallyDrop<T>, with the additional capability to perform partial moves out of T even if T: Drop. This would allow eliminating all use of unsafe from code like that linked above. That example could be rewritten like so:

fn into_arc(guard: Self) -> Arc<RwLock<T>> {
    let guard = ManuallyDrop::new(guard);

(One potential wrinkle is that unsafe APIs could theoretically be relying on the inability to perform such moves in safe code for soundness. I think it is unlikely that anyone is doing this in practice, however.)


The move-out-of-deref behavior of Box<T> is hard coded in the compiler and only works because of how special Box is in the compiler. I did strongly prefer if no other type gets the same treatment.

1 Like

ManuallyDrop is already compiler magic, and likely will continue to be forever. So I don't think a position of "avoid all compiler magic" is justified in this instance.

However it's a compiler magic that's long been desired for lots of other types, and everyone agrees, as far as I know, that there should be some kind of "DerefMove" that enables this behaviour.

So the "how should we do this for ManuallyDrop" seems to me like it should be "well we should figure out DerefMove and use it here", rather than "let's add something else to the magic deref bucket".


My proposal would allow partial moves out of the T inside ManuallyDrop<T> even if T: Drop, which is something that a more general DerefMove would not permit on its own. Compiler magic would remain necessary.


One purpose of DerefMove would be to allow things like &move dyn FnOnce to be used, so yes, I think the general idea is that you’d be able to move T: Drop values as well.

That would be a full move, not a partial move.

1 Like

It’s a partial move in the parent frame. You want to be able to do something like equivalent to (contrived)

let pairOfFns = getTwoFns();
f(&move pairOfFns.0);

Or did you switch which T you were talking about? I assumed you meant the T inside ManuallyDrop<T>, but maybe you meant the entire type ManuallyDrop<Inner> or Box<Inner>.

I am not 100% certain what is going on in your example, but what I intend is:

struct Foo(String, String);

impl Drop for Foo {
    fn drop(&mut self) {}

let foo = Foo("a".to_owned(), "b".to_owned());
let manual = ManuallyDrop::new(foo);
let a = manual.0; // Partial move


On the other hand, DerefMove has been discussed since at least 2015 and has made almost no progress toward shipping because of the difficulty of providing this API. Adding one more already-lang-item type, with a particularly strong motivation, to a behavioral special case that already exists, may be a prudent trade off.


The real question is why do we need ManuallyDrop just to move a single field out of a struct. Now, there is likely no other way to do it if you intend to drop the struct later. But many uses of ManuallyDrop are for the Drop impls themselves, where the struct will be destroyed anyway. Ideally the compiler should just know not to call recursive drop on already moved-from fields.

I know there were many discussions about this issue, but having read some of them, I am left with no understanding what is the current status of this change, and what are the blocking issues. It seemed like it just dropped off the radar at some point, with ManuallyDrop being a good-enough solution.

Similarly, there were proposals about allowing to destruct structs into their fields without calling Drop on the struct itself.

Does anyone know what happened to those proposals?

It's not about the recursive drop, if a type is impl Drop then it must be a complete value when Drop::drop is called on it, if you move a field out then the compiler cannot insert the normal drop call for the value itself. ManuallyDrop can be used as a marker "yes, I know this type would normally call Drop, but in this case I want to skip that and destructure it manually".

One issue I see is that ManuallyDrop seems to suppress the recursive-field-drop too, presumably in the last example this would leak the "b".to_owned() since it was never moved from.

That's not what I'm talking about.

  1. This code doesn't compile, even though the compiler could allow to destruct Foo into components and omit drop glue for x.
fn main() {
    struct Foo(Vec<u32>);
    impl Drop for Foo {
        fn drop(&mut self) {}
    let x = Foo(vec![]);
    let Foo(y) = x;
  1. In Drop impls, there is no issue of passing in an incomplete object. The object was already passed in, but I can't move a field out of it, because the method takes a &mut self, and because the compiler expects to drop all fields recursively.
struct Foo(String, String);

impl Drop for Foo {
  fn drop(&mut self) {
    std::mem::take(&mut self.0);

let mut foo = Foo("a".into_owned(), "b".into_owned());
// Double-free when `drop(&mut foo)` runs

You're not even trying to understand what I wrote.

I tried, but I don’t understand why you think it’s ok to call Drop on a partially moved value, you are definitely able to move a field out as demonstrated since you are given &mut self.

Whether x has drop glue called would then depend on whether Foo.0: Copy, which seems like a massive footgun.

I'm not suggesting dropping a partially moved value. I'm suggesting allowing to omit drop if a value was partially moved. For correctness reasons, this should require some explicit syntax, like a fully destructuring binding or some special keyword, but otherwise the concept is sound.

For values inside of Drop impl, the Self is already being dropped, so the objection also doesn't apply.

That’s what my understanding of this proposal is, using ManuallyDrop as the explicit syntax to omit the Drop call and then allow for partial moving.


RFC posted: Move out of deref for `ManuallyDrop` by Jules-Bertholet · Pull Request #3466 · rust-lang/rfcs · GitHub