Skip to content

RFC: Add MaybeDropped<T>#3918

Open
ionicmc-rs wants to merge 4 commits intorust-lang:masterfrom
ionicmc-rs:maybe-dropped
Open

RFC: Add MaybeDropped<T>#3918
ionicmc-rs wants to merge 4 commits intorust-lang:masterfrom
ionicmc-rs:maybe-dropped

Conversation

@ionicmc-rs
Copy link

@ionicmc-rs ionicmc-rs commented Feb 10, 2026

Adds a type for representing values whose destructor may have ran already.

Important

When responding to RFCs, try to use inline review comments (it is possible to leave an inline review comment for the entire file at the top) instead of direct comments for normal comments and keep normal comments for procedural matters like starting FCPs.

This keeps the discussion more organized.

Rendered

@ionicmc-rs ionicmc-rs changed the title MaybeDropped<T> RFC: Add MaybeDropped<T> Feb 10, 2026
@ehuss ehuss added T-lang Relevant to the language team, which will review and decide on the RFC. T-libs-api Relevant to the library API team, which will review and decide on the RFC. labels Feb 10, 2026

> - What parts of the design do you expect to resolve through the implementation of this feature before stabilization?

- How will we expose the public API?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did I really just read an RFC for a new unsafe standard library API that left the questions of what that new API should actually look like, and under what conditions it's going to be safe to use or UB, entirely as an unresolved question??

Comment on lines 27 to 39
Take this example, from the standard library (simplified)

```rust
pub struct Fuse<I> {
iter: I
}
```

The actual iterator is not needed after it yields `None` once, so in this case (not in the standard library,
however, due to a possible breaking change), We can replace the `I` with a `MaybeDropped`, which can optimize code by dropping the value early
(eg. by freeing allocator space, releasing a lock, etc.)

But you may realize this is exactly the same as using an `Option` (Which `Fuse` does). However, the following point contradicts this.
Copy link
Member

@steffahn steffahn Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ignoring the confusing wording and weirdly modified version of Fuse in the code snipped, I fail to see how a type which is all about cases where dropped-ness is tracked elsewhere (as the subsequent paragraph already notes1) in the case of Fuse where there is no “elsewhere”, for lack of any additional fields

Footnotes

  1. the relevant sentence:

    types/systems which would need MaybeDropped likely already have a means of tracking whether T is still needed or not elsewhere

@clarfonthey
Copy link

clarfonthey commented Feb 11, 2026

I have to ask, genuinely, did you write this? And, similarly, did you fully understand what you're writing about?

This gives me LLM vibes all over and unlike something that was machine-translated or written by a non-native English speaker, it just doesn't strike me as being written by someone who actually understands the purpose of the related APIs being mentioned (MaybeUninit, ManuallyDrop) or their behaviour profiles. At least in those cases, while there might be an LLM "vibe" to the text, it would at least be coherent and demonstrate understanding, and this is not.

I always feel bad making these types of assertions because it's almost impossible to tell the difference sometimes between someone who has genuinely tried their best, and someone who has not tried at all and is just wasting everyone's time. In the former case, I think that people should be given the support they need to write good proposals and help contribute to the language, but I just can't drop the suspicion that it's the latter case.

For example:

MaybeUninit implies a value is initialized once and never goes back, having the following lifecycle.

This is incorrect. In fact, this is explicitly the reason why, for example, transmuting &mut T to &mut MaybeUninit<T> is unsound: you can write MaybeUninit::uninit() directly to such a reference and uninitialize memory that is supposed to always be initialized. (Note: I actually made this mistake a while ago! So it's reasonable to make, just, not when you're writing a proposal to add something new based upon behaviour you misunderstood.)

For some cases, early drop is required; for example:

  • Locks
  • Allocations (in performance critical code)
  • Mechanisms requiring a value is dropped.

Early drop is required, in cases where… a value is dropped? This is definitely an example of something that might have just been lost in translation, so, I'm not going to put too much weight on it, but regardless, this is definitely a common case of something LLMs love to do, which is list examples that definitely apply, just trust me without any additional reasoning.

MaybeDropped<T> is also FFI safe if T is FFI safe, this allows for an FFI safe transfer of data that has possibly already been dropped.
This can also allow for an FFI safe Option, with additional argmuents

What do you even mean? FFI-safe transfer of data that has possibly been dropped? We don't even have a concept of ownership for FFI so this argument just makes no sense.

Users would use MaybeDropped just like a MaybeUninit, in reverse.

Take this example:

struct OnceImage {
    loader: MaybeDropped<ImageLoader>,
    stored: Option<Image> // drop state of `loader` is stored here.
}

suppose ImageLoader has alot of open resources:

  • locks
  • OS requests
  • File Descriptors
  • Services

the loader is only used once, while OnceImage can be long-lived. It makes no sense here to keep the loader for the lifetime of OnceImage.
And stored tracks the drop-state of loader, so it would be redundant to store it twice.

Again, this just fundamentally misunderstands how Rust's data model works at the most basic level. Yes, it would make sense to not keep a very complicated loader after you've loaded something, so, you just drop it. You drop the loader. It's gone. You don't need to store its corpse anywhere, because you've dropped it.

This is how a large number of APIs work: you have a loader which is called by-value because doing so consumes the loader: the loader is dropped, and the loaded value is returned in its place. This is coming up with a solution to a problem that doesn't even make sense to ask.

The following is not undefined behaviour:

  • Creating a MaybeDropped that is already dropped. (MaybeDropped::dropped)
  • Dropping a MaybeDropped (so long it is the first time)
  • Obtaining raw pointers to the inner memory.

the following is undefined behaviour:

  • Dropping a MaybeDropped twice.
  • Creating an uninitialized MaybeDropped
  • any access to the inner memory if it is already dropped.
  • Obtaining references to the inner memory (even if not used)
  • Writing a value to the MaybeDropped after it has already been dropped.

This fundamentally misunderstands Rust's unsafe guidelines, which already have not been fully defined:

  • Dropping something twice is always UB, and in an API suggestion like this, it's reasonable to repeat. (This one is okay.)
  • You say that creating an uninitialized MaybeDropped is UB, but then you explicitly define it later as equivalent to MaybeUninit<ManuallyDrop<T>>, which means it is explicitly not UB. Unions with a ZST field automatically may be maybe-uninit, and MaybeUninit has no special treatment in this regard.
  • Accessing memory is vague and, ironically, undefined. Yes, it is UB to read a dropped value in general, but the fuzziness with words here confuses things. And on that note…
  • Obtaining "references" to the "inner memory?" What "inner memory?" The value has identically the same pointer, and even though you didn't explicitly put #[repr(transparent)] on the example, it's pretty clear that it's the same memory. Do you mean transmuting a pointer, since you can't just directly access a private field? Because, again, this is one of the vague parts of the unsafe guidelines which nobody has fully decided, where technically transmuting references and doing a deref and re-ref (&*ptr) should probably both mean accessing a reference, but might not always, and that needs to be decided. (for any wg-unsafe-guidelines folks reading this, please feel to correct me if this actually has been decided and I'm wrong)
  • Sure, we could make a type where it's a write-once, drop-once invariant, but that's just an invariant you have to personally uphold, and it affects which methods are unsafe and which aren't. And in this case, it feels pretty hard to do, considering how you can always write to mutable references unless you incorporate the lifetimes inside your struct, which you did not do.

I really, really want to give the benefit of the doubt, but after reading your ABI descriptors RFC where you similarly tried to come up with a solution you openly admitted to not understand, I really do not want to read your RFC in good faith because it really does feel like a case of someone who didn't read any reference material to understand the problem, and didn't actually write the RFC to solve the problem. And I don't think we should be encouraging that kind of behaviour.

Again, I want to reiterate that it's okay to not understand English or to use machine translation tools to interact with the community. And it's okay to not be an expert at Rust. But if you want to propose a targeted change to Rust, you need to understand why that change is good, and I don't think that's the case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe you can simply implement this whole type, assuming the implementation as you describe it here, as a simple wrapper around MaybeUninit<T>. And with the changes to ManuallyDrop from RFC 3336 becoming available soon (AFAICT), eventually that can become a wrapper around ManuallyDrop<T>. Types like ManuallyDrop<T> and MaybeUninit<T> are in the standard library because the former needs some compiler magic to be implemented, and the latter is used a lot and appears in argument or return types in a number of other standard library APIs. The standard library is deliberately kept somewhat minimalistic – I don’t see why the MaybeDropped<T> type here shouldn’t first onl[y be part of some downstream crate, where it can prove itself useful.

Unless there is any fundamentally “magic” property that this MaybeDropped type is supposed to have why it can not simply be implemented downstream, but I don’t see any mention of such a thing in this RFC.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely, parsing out the most I could get from the RFC, it feels like the proposal is just a misunderstanding of how ManuallyDrop works, and ManuallyDrop would work for the cases mentioned.

And the actual proposed definition in the reference-level description is literally just MaybeUninit<ManuallyDrop<T>>, so 🤷🏻

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do realize now I wrote this RFC in confusing wording (and i wrote like an LLM for some reason, i don't know why). Im also not a native english speaker, I will try to rewrite better and update it now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-lang Relevant to the language team, which will review and decide on the RFC. T-libs-api Relevant to the library API team, which will review and decide on the RFC.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants