# Destructuring Droppable structs

**URL:** https://internals.rust-lang.org/t/destructuring-droppable-structs/20993
**Category:** language design
**Created:** [June 8, 2024, 10:36am UTC](https://internals.rust-lang.org/t/destructuring-droppable-structs/20993 "2024-06-08T10:36:14Z")
**Posts on this page:** 20
**Page:** 1

<div class="post-metadata">

### Author: ![matthieum](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/matthieum/32/4023_2.png) [@matthieum](https://internals.rust-lang.org/u/matthieum)
#### Post date: [June 8, 2024, 10:36am UTC](https://internals.rust-lang.org/t/destructuring-droppable-structs/20993/1 "2024-06-08T10:36:15Z")

</div>

### Previous topics:

- From 2019: [[Pre-RFC] Destructuring values that impl Drop](https://internals.rust-lang.org/t/pre-rfc-destructuring-values-that-impl-drop/10450).
- From 2014: [Destructuring structs which implement Drop - #12 by tbu](https://internals.rust-lang.org/t/destructuring-structs-which-implement-drop/137/12).

### Proposal

Turn error E0509 into a lint, _warning_ by default:

- This still allows users to catch unintended destructuring which would bypass `Drop`.
- While allowing users to override the compiler, when desired.

_This differs from the 2019 proposal in that instead of a custom attribute and a lint, this proposal contends that just a lint is enough, since lints can be tuned. The 2019 proposal did list this possibility in its conclusion._

### Motivation

The following code, while seemingly reasonable, is rejected:

```rust
struct Dummy {
    foo: i32,
    bar: String,
}

impl Drop for Dummy {
    fn drop(&mut self) {
        println!("Dropping Dummy {{ foo: {}, bar: {} }}", self.foo, self.bar);
    }
}

fn main() {
    let dummy = Dummy { foo: 2, bar: String::from("3") };
    let Dummy { foo, bar } = dummy;
    
    println!("{foo} + {bar} = 5");
}

```

Currently leads to:

```txt
error[E0509]: cannot move out of type `Dummy`, which implements the `Drop` trait
  --> src/main.rs:14:30
   |
14 | let Dummy { foo, bar } = dummy;
   | --- ^^^^^ cannot move out of here
   | |
   | data moved here
   | move occurs because `bar` has type `String`, which does not implement the `Copy` trait
   |
help: consider borrowing the pattern binding
   |
14 | let Dummy { foo, ref bar } = dummy;
   | +++

For more information about this error, try `rustc --explain E0509`.
error: could not compile `playground` (bin "playground") due to 1 previous error

```

I do think _warning_ the user that they may accidentally be skipping the `Drop` implementation of the type is a good idea, they may not have intended to. I do think that making this lint into a hard error is taking things one step too far.

The **Rationale** section of the 2019 proposal already offers a comprehensive list of reasons for doing this.

### Q & A

> But invoking `drop` on the instance would be UB!

Why yes, obviously. Hence the compiler wouldn't.

If an instance is moved then `drop` isn't invoked on it. If a destructuring is partial, then `drop` is only invoked on the fields not matched.

The new "move-destructure-for-droppable-type" capability would just follow those two existing rules.

> But this allows users to bypass `drop`!

Users can already bypass drop, and do so routinely. The very `forget` function allows a user to bypass `drop`, and it's otherwise possible to leak types, etc..

> Should it be restricted to the crate or module where `drop` is defined?

There'd be no point, really.

Destructuring is already restricted via visibility rules: only fields that are accessible via `.` are accessible via destructuring.

Users can already, painstakingly, swap out visible fields then `forget` the instance. Destructuring would not give them any more power, it'd just make the code easier to read.

The warning _would_ remind users that bypassing `Drop` may lead to leaks, and from then on it's up to them.

---

<div class="post-metadata">

### Author: ![RustyYato](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/rustyyato/32/13627_2.png) [@RustyYato](https://internals.rust-lang.org/u/RustyYato)
#### Post date: [June 8, 2024, 4:52pm UTC](https://internals.rust-lang.org/t/destructuring-droppable-structs/20993/2 "2024-06-08T16:52:48Z")

</div>

It's currently not possible to bypass Drop for a type and drop any of it's private fields. So I think this form should require all fields are present (at least initially), and shouldn't work if you can't list all fields (some are private or type is `non_exhaustive`).

For example, this should not work. Even if a type has no fields.

```rust
let Foo { .. } = bar;

```

My worry is that some unsafe code may temporarily break safety invariants, which are fixed up in Drop before fields are dropped. Such types would become unsound if anyone could drop fields while skipping the Drop.

---

<div class="post-metadata">

### Author: ![Jules-Bertholet](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/jules-bertholet/32/10671_2.png) [@Jules-Bertholet](https://internals.rust-lang.org/u/Jules-Bertholet)
#### Post date: [June 9, 2024, 12:00am UTC](https://internals.rust-lang.org/t/destructuring-droppable-structs/20993/3 "2024-06-09T00:00:24Z")

</div>

I have an RFC posted for exactly this functionality, though it takes a different approach: [Move out of deref for `ManuallyDrop` by Jules-Bertholet · Pull Request #3466 · rust-lang/rfcs · GitHub](https://github.com/rust-lang/rfcs/pull/3466)

---

<div class="post-metadata">

### Author: ![Jules-Bertholet](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/jules-bertholet/32/10671_2.png) [@Jules-Bertholet](https://internals.rust-lang.org/u/Jules-Bertholet)
#### Post date: [June 9, 2024, 2:33am UTC](https://internals.rust-lang.org/t/destructuring-droppable-structs/20993/4 "2024-06-09T02:33:24Z")

</div>

As discussed in the [Internals thread](https://internals.rust-lang.org/t/move-out-of-deref-for-manuallydrop/19216/) for this RFC, the issue with "just don't call `Drop` upon partial move" is that whether an assignment is a partial move or not depends on whether the field type is `Copy`. And that can change in any minor version, and can also depend on lifetimes, which aren't allowed to influence monomorphization:

```rust
#[derive(Clone)]
struct Foo<'a>(&'a ());

impl Copy for Foo<'static> {}

struct Bar<'a>(Foo<'a>);

impl<'a> Drop for Bar<'a> {
    fn drop(&mut self) {
        println!("goodbye world");
    }
}

fn do_thing<'a>(bar: Bar<'a>) {
    drop(bar.0); // This is a move only when `'a` is not `'static`
}

```

(interestingly, it seems the compiler has a bug here: [Implementing `Copy` can be a breaking change · Issue #126179 · rust-lang/rust · GitHub](https://github.com/rust-lang/rust/issues/126179))

---

<div class="post-metadata">

### Author: ![binarycat](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/binarycat/32/12210_2.png) [@binarycat](https://internals.rust-lang.org/u/binarycat)
#### Post date: [June 9, 2024, 3:21am UTC](https://internals.rust-lang.org/t/destructuring-droppable-structs/20993/5 "2024-06-09T03:21:04Z")

</div>

TIL you can destructure a struct with private fields

[playground example](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=f7917e9b4439cfd6fb5ff907ad785d27)

---

<div class="post-metadata">

### Author: ![whitequark](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/whitequark/32/12256_2.png) [@whitequark](https://internals.rust-lang.org/u/whitequark)
#### Post date: [June 9, 2024, 6:32am UTC](https://internals.rust-lang.org/t/destructuring-droppable-structs/20993/6 "2024-06-09T06:32:51Z")

</div>

Thanks for picking this up!

---

<div class="post-metadata">

### Author: ![matthieum](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/matthieum/32/4023_2.png) [@matthieum](https://internals.rust-lang.org/u/matthieum)
#### Post date: [June 9, 2024, 11:24am UTC](https://internals.rust-lang.org/t/destructuring-droppable-structs/20993/7 "2024-06-09T11:24:08Z")

</div>

> [@RustyYato](#):
>
> My worry is that some unsafe code may temporarily break safety invariants, which are fixed up in Drop before fields are dropped. Such types would become unsound if anyone could drop fields while skipping the Drop.

How would such code handle using `std::mem::take` or `std::mem::replace` on public fields to swap them out, then forgetting the value?

Those are all safe operations, so unsafe code should be sound in their presence (or is broken).

> [@Jules-Bertholet](#):
>
> And that can change in any minor version, and can also depend on lifetimes, which aren't allowed to influence monomorphization:

I'm puzzled by the snippet: isn't move-vs-copy dependent on the field type (`&'a ()`, always `Copy`) instead of the struct type? (Or did you mean to take `Bar` as an argument?)

If the copyability of `Foo` influences things, then I'd expect generics to require that `Foo` be constrained by `Copy` for copy to take place, so that in your example `drop(foo.0)` is always a move regardless of whether `'a` is `'static` because there's no `where Foo<'a>: Copy` bound.

> [@Jules-Bertholet](#):
>
> I have an RFC posted for exactly this functionality

In the `into_bar` function, you want `self`, not `foo`, as the name of the argument to `ManuallyDrop`.

I would favor a lint, instead of `ManuallyDrop`, due to the extra flexibility that white-listing a lint allows. Specifically, it'd be possible to white-list at module level, or function level, instead of wrapping every single instance in `ManuallyDrop` which is quite heavy-handed. But that's merely syntax bikeshedding, in a sense, and I'd be quite happy with your RFC as is.

I am more concerned by the examples brought by @fee1-dead and @ijackson (Hyrum's law...), though the fact that `partial-borrow` and `replace-with` are incompatible seem to require arbitrage from the language team already: clearly at least one of them makes an assumption it should not. In particular, I'm wondering how their code expects to deal with code taking/replacing the public fields then forgetting the value -- all safe operations.

---

<div class="post-metadata">

### Author: ![Jules-Bertholet](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/jules-bertholet/32/10671_2.png) [@Jules-Bertholet](https://internals.rust-lang.org/u/Jules-Bertholet)
#### Post date: [June 9, 2024, 12:05pm UTC](https://internals.rust-lang.org/t/destructuring-droppable-structs/20993/8 "2024-06-09T12:05:16Z")

</div>

> [@matthieum](#):
>
> (Or did you mean to take `Bar` as an argument?)

Yes, exactly, fixed now. Sorry

---

<div class="post-metadata">

### Author: ![RustyYato](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/rustyyato/32/13627_2.png) [@RustyYato](https://internals.rust-lang.org/u/RustyYato)
#### Post date: [June 9, 2024, 2:04pm UTC](https://internals.rust-lang.org/t/destructuring-droppable-structs/20993/9 "2024-06-09T14:04:14Z")

</div>

> [@matthieum](#):
>
> How would such code handle using `std::mem::take` or `std::mem::replace` on public fields to swap them out, then forgetting the value?
> 
> Those are all safe operations, so unsafe code should be sound in their presence (or is broken).

Hence why I said private fields. But types may have a mix of public and private fields.

Let's take a simple (if contrived) example:

```rust
struct PrintOnDrop(String);

struct Foo {
    pub name: String,
    data: PrintOnDrop
}

impl Drop for PrintOnDrop {
    fn drop(&mut self) {
        println!("{}", self.0);
    }
}

impl Drop for Foo {
    fn drop(&mut self) {
        self.data.0.clear();
        self.data.0.clone_from(&self.name);
    }
}

```

Let's say you moved `name` out of `Foo`, it's public after all. Then you couldn't run `Drop for Foo`. So if `foo.data.0` contained invalid utf-8 bytes, there would be UB if `Drop for PrintOnDrop`. However, right now this api should be sound because, it's not possible to run `Drop for PrintOnDrop` without running `Drop for Foo`, which fixes up `foo.data`

---

<div class="post-metadata">

### Author: ![matthieum](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/matthieum/32/4023_2.png) [@matthieum](https://internals.rust-lang.org/u/matthieum)
#### Post date: [June 9, 2024, 5:41pm UTC](https://internals.rust-lang.org/t/destructuring-droppable-structs/20993/10 "2024-06-09T17:41:36Z")

</div>

I see the problem now. So the core issue is not about not running any implementation of `Drop`, but specifically about running `Drop` for fields without running `Drop` for the outer struct.

It does feel like quite a contrived situation. I have seen fix-ups on the `Drop`, with the pre-pooping your pants idiom, but those are specifically about keeping things safe without `Drop`.

I guess it means it's worth checking exactly how many occurrences of this strange pattern exists in the wider ecosystem, and whether they're worth it. It's hopefully a sufficiently exotic pattern that it won't prove an impediment.

---

<div class="post-metadata">

### Author: ![binarycat](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/binarycat/32/12210_2.png) [@binarycat](https://internals.rust-lang.org/u/binarycat)
#### Post date: [June 9, 2024, 6:18pm UTC](https://internals.rust-lang.org/t/destructuring-droppable-structs/20993/11 "2024-06-09T18:18:52Z")

</div>

why not just prevent destructuring structs that:

1. have private fields (or are `#[non_exhaustive]`)
2. implement `Drop`

this should cover up this corner case, and imo destructuring is mostly useful for structs that are entirly public anyways.

---

<div class="post-metadata">

### Author: ![matthieum](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/matthieum/32/4023_2.png) [@matthieum](https://internals.rust-lang.org/u/matthieum)
#### Post date: [June 10, 2024, 4:18pm UTC](https://internals.rust-lang.org/t/destructuring-droppable-structs/20993/12 "2024-06-10T16:18:16Z")

</div>

Do you really mean private or do you rather mean non-accessible?

Private seems like a non-starter, since it seems perfectly reasonable for a developer of a crate to wish to pattern-match on their own structs. After all, a motivating usecase is specifically for such developers to be able to provide conversion/finalization methods without having to resort to `unsafe` code with `ManuallyDrop`, or to `Option`.

Non-accessible thus seems more appealing... but when a major usecase is the above-mentioned users, and they can still shoot themselves in the foot, it's unclear whether the restriction is worth it.

Perhaps, then, the better solution is just to allow one to opt-out of move-destructuring at the struct level (but still allow ref-destructuring). Then all users are equally safeguarded.

But whether such an opt-out is worth it is very much in question for me. So far we only have 1 unpublished crate and 1 rarely used (and potentially unsound) crates as examples of code that use this strange practice, and both are essentially "abusing" it in order to emulate partial borrows.

---

<div class="post-metadata">

### Author: ![djc](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/djc/32/1592_2.png) [@djc](https://internals.rust-lang.org/u/djc)
#### Post date: [June 10, 2024, 9:56pm UTC](https://internals.rust-lang.org/t/destructuring-droppable-structs/20993/13 "2024-06-10T21:56:53Z")

</div>

I think the simplest version here would be that only a method attached to an inherent impl (so, only local types) could be allowed to do this. That still seems like a nice win, and it seems okay for the local crate to decide that they might provide an alternative way of "dropping" the value. Have definitely run into the need for this before.

---

<div class="post-metadata">

### Author: ![mathstuf](https://avatars.discourse-cdn.com/v4/letter/m/958977/32.png) [@mathstuf](https://internals.rust-lang.org/u/mathstuf)
#### Post date: [June 11, 2024, 11:24am UTC](https://internals.rust-lang.org/t/destructuring-droppable-structs/20993/14 "2024-06-11T11:24:00Z")

</div>

> [@djc](#):
>
> That still seems like a nice win, and it seems okay for the local crate to decide that they might provide an alternative way of "dropping" the value. Have definitely run into the need for this before.

This now reminds me of linear types (here, with a non-`pub` alternative "sink" mechanism). What's the interaction of this with linear type proposals (I'm not sure of what progress/feasibility they have)? Maybe they would cover this feature in some way?

---

<div class="post-metadata">

### Author: ![matthieum](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/matthieum/32/4023_2.png) [@matthieum](https://internals.rust-lang.org/u/matthieum)
#### Post date: [June 11, 2024, 4:09pm UTC](https://internals.rust-lang.org/t/destructuring-droppable-structs/20993/15 "2024-06-11T16:09:04Z")

</div>

> [@djc](#):
>
> I think the simplest version here would be that only a method attached to an inherent impl (so, only local types) could be allowed to do this. That still seems like a nice win, and it seems okay for the local crate to decide that they might provide an alternative way of "dropping" the value. Have definitely run into the need for this before.

Very pragmatic, I like it.

I think it'd still make sense to have the warning by default -- given the consequences of accidentally not dropping -- so the end result for move-destructuring in the presence of a Drop implementation would be:

- Outside of inherent `impl`: hard error.
- Inside inherent `impl`: lint, warn by default.

---

<div class="post-metadata">

### Author: ![dhm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/dhm/32/4879_2.png) [@dhm](https://internals.rust-lang.org/u/dhm)
#### Post date: [June 11, 2024, 6:37pm UTC](https://internals.rust-lang.org/t/destructuring-droppable-structs/20993/16 "2024-06-11T18:37:42Z")

</div>

FWIW, it's already possible to define a helper macro for this task:

```rs
struct Point { x: i32, y: i32 }

impl Drop for Point {
    fn drop(&mut self) {
        unreachable!()
    }
}

let p = Point { x: 42, y: 27 };
destructuring_drop! {
    let Point {
        x: value,
        y: _,
    } = p;
}
assert_eq!(dbg!(value), 42);

```

By having the macro expand to the following `unsafe` code:

```rs
let (value, _) = match *ManuallyDrop::new(p) {
    Point { ref x, ref y } => unsafe {
        (
            <*const _>::read(x),
            <*const _>::read(y),
        )
    },
};

```

- 
### [Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=4c52b11558e1a4a7e834a88994361203)

Having the hard error become a lint (I don't care about its default polarity) seems like a less cumbersome and with-better-diagnostics way to achieve the same 🙂

---

<div class="post-metadata">

### Author: ![binarycat](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/binarycat/32/12210_2.png) [@binarycat](https://internals.rust-lang.org/u/binarycat)
#### Post date: [June 11, 2024, 7:31pm UTC](https://internals.rust-lang.org/t/destructuring-droppable-structs/20993/17 "2024-06-11T19:31:54Z")

</div>

> [@matthieum](#):
>
> Perhaps, then, the better solution is just to allow one to opt-out of move-destructuring at the struct level (but still allow ref-destructuring). Then all users are equally safeguarded.

you can already pattern match on a reference to a droppable struct, so i'm not entirly sure what you mean.

```rust
struct Dummy {
    foo: i32,
    bar: String,
}

impl Drop for Dummy {
    fn drop(&mut self) {
        println!("Dropping Dummy {{ foo: {}, bar: {} }}", self.foo, self.bar);
    }
}

fn main() {
    let dummy = Dummy { foo: 2, bar: String::from("3") };
    let Dummy { ref foo, ref bar } = &dummy;
    println!("{foo} + {bar} = 5");
}

```

---

<div class="post-metadata">

### Author: ![matthieum](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/matthieum/32/4023_2.png) [@matthieum](https://internals.rust-lang.org/u/matthieum)
#### Post date: [June 12, 2024, 4:10pm UTC](https://internals.rust-lang.org/t/destructuring-droppable-structs/20993/18 "2024-06-12T16:10:50Z")

</div>

> [@binarycat](#):
>
> you can already pattern match on a reference to a droppable struct, so i'm not entirly sure what you mean.

Just a reminder that whatever changes should obviously NOT change this.

---

<div class="post-metadata">

### Author: ![withoutboats](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/withoutboats/32/4560_2.png) [@withoutboats](https://internals.rust-lang.org/u/withoutboats)
#### Post date: [June 23, 2024, 11:56am UTC](https://internals.rust-lang.org/t/destructuring-droppable-structs/20993/19 "2024-06-23T11:56:59Z")

</div>

(NOT A CONTRIBUTION)

I wrote about how I thought this rule of Rust should change in this post about the design of ownership: [Ownership](https://without.boats/blog/ownership/). It contais a pretty compelling motivating example that I got from David Tolnay from serde\_json: the Value type is meant to be destructured, but it also wants to have a custom destructor.

> [@djc](#):
>
> I think the simplest version here would be that only a method attached to an inherent impl (so, only local types) could be allowed to do this. That still seems like a nice win, and it seems okay for the local crate to decide that they might provide an alternative way of "dropping" the value. Have definitely run into the need for this before.

This would fail to solve the example from serde\_json, which is a public type meant to support destructuring as part of its public API. In my opinion, it does not make sense that drop prevents destructuring; if you want to make it illegal to destructure a type outside of this module, you should use privacy.

---

<div class="post-metadata">

### Author: ![matthieum](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/matthieum/32/4023_2.png) [@matthieum](https://internals.rust-lang.org/u/matthieum)
#### Post date: [June 23, 2024, 1:10pm UTC](https://internals.rust-lang.org/t/destructuring-droppable-structs/20993/20 "2024-06-23T13:10:59Z")

</div>

While I would tend to agree with you, unfortunately, if you read the previous attempts linked at the top, and the discussion here, you'll note that the previous attempts failed because of concerns for:

1. API: a type should only be destructurable if the API designer wishes it to be, as the API designer may be rely on `Drop`.
2. Safety: The reliance on `Drop` preventing destructuring may be the key that upholds safety invariants.

There's value in offering _opt-in_ destructuring -- under full control of the API designer -- while privacy is an opt-out.

It's also not clear to me how privacy would work with `enum`: should an `enum` always be destructurable? You can't have a private variant, or private field, in an `enum`... so there's a loss of granularity there.

* * *

Another possibility would be an opt-in attribute, but it has the disadvantage (in its simplest form) of not giving control over _who_ can destructure. Perhaps the API designer would want the liberty to destructure themselves _without_ granting this liberty to the API users, so they can enforce that some things happen on destructuring/drop.

It may be possible to compound a simple opt-in attribute with privacy, but this still runs into the issue of the lack of granularity with `enum`.

* * *

@djc's simple solution is the opposite. It only grants the API designer the ability to destructure an no one else. This is opt-in, and allow the designer to enforce side-effects on destructuring.

It has the advantage of working with both `enum` and `struct`, but the disadvantage of not allowing the users to destructure.

Perhaps it should be combined with a simple attribute allowing full destructuring powers to the users for completeness.

[Next page](https://internals.rust-lang.org/t/destructuring-droppable-structs/20993.md?page=2)
