Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
257 changes: 257 additions & 0 deletions text/3921-place-traits.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
- Feature Name: `place_traits`
- Start Date: 2026-01-23
- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/3921)
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000)

## Summary
[summary]: #summary

This RFC introduces the `Place` trait. This trait allows arbitrary types to implement the
special derefence behavior of the `Box` type. In particular, it allows an arbitrary type
to act as an owned place allowing values to be (partially) moved out and moved back in
again.

## Motivation
[motivation]: #motivation

Currently the Box type is uniquely special in the rust ecosystem. It is unique in acting
like an owned variable, and allows a number of special optimizations for direct
instantiation of other types in the storage allocated by it.

This special status comes with two challenges. First of all, Box gets its special status
by being deeply interwoven with the compiler. This is somewhat problematic as it requires
exactly matched definitions of how the type looks between various parts of the compiler
and the standard library. Moving box over to a place trait would provide a more pleasant
and straightforward interface between the compiler and the box type, at least in regards
to the move behavior.

Second, it is currently impossible to provide a safe interface for user-defined smart
pointer types which provide the option of moving data in and out of it. There have been
identified a number of places where such functionality could be interesting, such as when
removing values from containers, or when building custom smart pointer types for example
in the context of an implementation of garbage collection.

## Guide-level explanation
[guide-level-explanation]: #guide-level-explanation

This proposal introduces a new unsafe trait `Place`:
```rust
unsafe trait Place: DerefMut {
fn place(&mut self) -> *mut Self::Target

Choose a reason for hiding this comment

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

So, it's a little strange why this method is necessary when it appears that any implementation would just put a call to deref_mut here: the mutable reference would coerce to a pointer, and casting to a pointer obviously removes all reference to lifetimes and lets the compiler do whatever it wants with it.

Copy link
Author

Choose a reason for hiding this comment

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

Best I understand the semantics of mutable references, it would be unsound to have one to a value that is uninitialized. And the semantics of moving in and out of the Place would involve calling this function in cases where *mut Self::Target would then have to point to something that is uninitialized.

Choose a reason for hiding this comment

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

Hmm, but wouldn't that mean that you have to take *mut self, not &mut self? What you're saying makes sense, but it's unclear how the pointer could be initialized when this function is called. Effectively, the mutable borrow ends after the function returns, so, it's totally valid for something that was originally &mut Self::Target to become initialized as long as the original lifetime has ended and it's only used as a pointer at that point.

Copy link
Author

Choose a reason for hiding this comment

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

Ok, I answered more completely in the other thread, see #3921 (comment). It basically comes down to that the argument to place is the Box-Like, which is (and should be, otherwise use of this trait is going to be a nightmare) still initialized, even though its Contents might not be. The non-initialized status of the Contents rules out the use of references for that, hence the extra function and the pointer.

}
```

The `Place` trait essentially allows values of the type to be treated as an already-
existing box. That is, they behave like a variable of the type `Deref::Target`, just
stored in a different location than the stack. This means that values of type
`Deref::Target` can be (partially) moved in and out of dereferences of the type, with the
borrow checker ensuring soundness of the resulting code. As an example, if `Foo`
implements `Place` for type `Bar`, the following would become valid rust code:
```rust
fn baz(mut x: Foo) -> Foo {
let y = *x;
*x = y.destructive_update()
x
}
```

When implementing this trait, the type itself effectively transfers some of the responsibilities for managing the value behind the pointer returned by `Place::place`, also called the content, to the compiler. In particular, the type itself should no longer count on the ccontent being properly initialized and dropable when its `Drop` implementation or `Place::place` implementation is called. However, the compiler still guarantees that, as long as the type implementing the place is always created with a value in it, and that value is never removed through a different mechanism than dereferencing the type, all other calls to member functions can assume the value to be implemented.

In general, the compilers requirements are met when
- The pointer returned by `place` should be safe to mutate through, and should be live
for the lifetime of the mutable reference to `self` passed to `Place::place`.
- On consecutive calls to `Place::place`, the status of whether the content is initialized should not be changed.
- Drop must not drop the contents, only the storage for it.
- Newly initialized values of the type implementing `Place` must have their content initialized.

There is one oddity in the behavior of types implementing `Place` to be aware of.
Automatically elaborated dereferences of values of such types will always trigger an abort
on panic, instead of unwinding when that is enabled. However, generic types constrained to
only implement Deref or DerefMut but not Place will always unwind on panics during
dereferencing, even if the underlying type also implements Place.

## Reference-level explanation
[reference-level-explanation]: #reference-level-explanation

This proposal introduces one new main language item, the traits `Place`. We also introduce a number of secondary language items which are used to make implementation easier and more robust, which we shall define as they come up below.

A type implementing the trait Place is required to act as a place for borrow checking. Throughout the rest of this text, the contents of the memory pointed at by the pointer returned by the `Place::place` function shall be refered to as the content of the place. For a type to satisfy the above requirement, its implementation must in particular guarantee that
- Safe code shall not modify the initialization status of the contents.
- Unsafe code shall preserve the initialization status of the contents between two derefences of teh type's values.
- Values of the place type for which the content is uninitialized shall not be able to be created in safe code.
Comment on lines +79 to +81

Choose a reason for hiding this comment

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

This is… a very confusing set of safety requirements considering how they're effectively already guaranteed by the intrinsic safety requirements of the language: you can't de-initialize the contents of something with a mutable reference, and unsafe code is expected to uphold this regardless of whether the reference is converted into a pointer or not.

Copy link
Author

@davidv1992 davidv1992 Feb 19, 2026

Choose a reason for hiding this comment

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

I agree that the formulation here is less than ideal, however there is something real that is asked here, that is unfortunately not guaranteed by the borrow checker alone. For example, if somebody defines the type InplaceBox as follows:

struct InplaceBox<T>(MaybeUninit<T>);

impl<T> Deref for InplaceBox<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        unsafe { self.0.assume_init_ref() }
    }
}

impl<T> DerefMut for InplaceBox<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        unsafe { self.0.assume_init_mut() }
    }
}

unsafe impl<T> Place for InplaceBox<T> {
    type NewArg = ();

    fn place(&mut self) -> *mut Self::Target {
        self.0.as_mut_ptr()
    }
}

Then as part of the unsafe contract for place they promise to for example not to do stuff like

#[derive(Debug, Clone, Copy)]
enum NonZero { One = 1, Two = 2 }

pub fn foo() {
    let ipb = InplaceBox(MaybeUninit::init(NonZero::One))
    println!("{:?}", *ipb); // Still OK, ipb contains something
    ipb.0 = MaybeUninit:zeroed(); // Should be forbidden, because borrow checker should still think following is ok.
    println!("{:?}", *ipb); // UB happens here now, since even though the borrow checker thinks this is fine, it is not, because of the line above.
}

Forbidding these sorts of shenanigans is what I am trying to capture with these requirements. If you have suggestions for how to better formulate that I'd love those.

Copy link

@clarfonthey clarfonthey Feb 19, 2026

Choose a reason for hiding this comment

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

I guess that, as I also pointed out in the other comment thread, it's not really clear how this UB is possible based upon what the API is trying to achieve. Yeah, that example is obviously UB, but it's unclear how the Place implementation needs these guarantees in order to work.

Like, no matter what, safe code should not be allowed to create an invalid value, and if you're specifically moving a value out of a place, you kind of necessitate that the container be dropped in some way as part of this process, so, further references will not be possible. For Box, this happens via deallocating the pointer, but it also requires running the drop glue for the field beforehand, and it needs to know whether that should have to occur or not.

Copy link

Choose a reason for hiding this comment

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

Possibly the trait could remain unstable itself while having a pointer-field based compiler generated implementation path, similar to CoerceUnsize and its CoercePointee's relationship. Consider that we may not need to name the trait explicitly in many use cases, only the compiler must be aware of the trait implementation for variables (that it then has a borrow tree for). This would solve two other issues:

  • The macro implemented in the compiler can initially verify a very narrow subset of types to qualify, for which the semantics we want are quite clear. The overlap seems quite large, too. For smart pointers with one pointer-like field from which to derives its behavior seems reasonably well-defined. The contentious method / MIR would be compiler generated, too, which means the meat of the unresolved question is punted to future relaxations of the macro or arbitrary user-defined derives.
  • We could define that SmartPointer<MaybeUninit<T>> is allowed to initialized SmartPointer<T> by filling the place (through compiler defined init-sequence support) and transmutation. This would be based on very similar layout requirements than placement but again the macro could annotate the type to guarantee them.

Copy link
Member

Choose a reason for hiding this comment

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

We could define that SmartPointer<MaybeUninit> is allowed to initialized SmartPointer by filling the place (through compiler defined init-sequence support) and transmutation. This would be based on very similar layout requirements than placement but again the macro could annotate the type to guarantee them.

You can currently move out of a Box<T> and then back into it again without moving the Box<T> itself. Transmuting requires moving it.

fn main() {
    let mut a = Box::new(Box::new(vec![0]));
    drop(**a); // Deinitialize the inner box without moving it.
    **a = vec![]; // Reinitialize the inner box without moving it.
}

Copy link
Author

Choose a reason for hiding this comment

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

I guess that, as I also pointed out in the other comment thread, it's not really clear how this UB is possible based upon what the API is trying to achieve. Yeah, that example is obviously UB, but it's unclear how the Place implementation needs these guarantees in order to work.

Like, no matter what, safe code should not be allowed to create an invalid value, and if you're specifically moving a value out of a place, you kind of necessitate that the container be dropped in some way as part of this process, so, further references will not be possible. For Box, this happens via deallocating the pointer, but it also requires running the drop glue for the field beforehand, and it needs to know whether that should have to occur or not.

Ok, so where I think my explanation is not clear enough is that perhaps I'm not making a distinction that maybe we should make more explicit. On the one hand we have the implementer of the Place trait, lets call it the Box-Like. Then separately there is the thing the Box-Like can be derefferenced into, lets call that the Contents.

The crux around the undefined behavior is that the initialization status of the Box-Like and its Contents are to an extent independent. Primarily, the Box-Like should always be initialized, in the sense that we are allowed to have references to the Box-Like without having UB. However, the Contents can at various times be uninitialized, such as when the previous value has been moved out of them but the next hasn't yet been moved back in. Using the Deref traits on those Box-Likes is UB, because the resulting reference is a reference to an uninitialized value, which is UB. Really the only reason you are allowed to dereference them is to move some new value in.

This matches what is currently already the case for Box. It is completely valid to have references to a Box whose Contents are uninitialized, that yields no UB (otherwise the Drop implementation for Box would exhibit undefined behavior). However, then using the Deref traits those boxes in any way DOES yield UB, as that process again produces a reference to uninitialized memory.

What the compiler, via the borrow checker, guarantees for Box, and what I propose it will also guarantee for Box-Likes, is that all this complexity with the Contents potentially being uninitialized, can remain hidden from the end user, so long as the implementation of the Box or Box-Like is "reasonable". In other words, that end users can just trust the borrow checker to prevent them from trying to read from an uninitialized Box or Box-Like.

That means however that we need to somehow put into words what it means for the implementation of such a type to be "reasonable", which is what these requirements try to capture. They basically come down to "don't mess with the Contents too much, the compiler will manage that for you", but making concrete rules for that is hard and I'd love input on how to clarify them.

Copy link
Author

Choose a reason for hiding this comment

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

Possibly the trait could remain unstable itself while having a pointer-field based compiler generated implementation path, similar to CoerceUnsize and its CoercePointee's relationship. Consider that we may not need to name the trait explicitly in many use cases, only the compiler must be aware of the trait implementation for variables (that it then has a borrow tree for). This would solve two other issues:

* The macro implemented in the compiler can initially verify a very narrow subset of types to qualify, for which the semantics we want are quite clear. The overlap seems quite large, too. For smart pointers with _one_ pointer-like field from which to derives its behavior seems reasonably well-defined. The contentious method / MIR would be compiler generated, too, which means the meat of the unresolved question is punted to future relaxations of the macro or arbitrary user-defined derives.

* We could define that `SmartPointer<MaybeUninit<T>>` is allowed to initialized `SmartPointer<T>` by filling the place (through compiler defined init-sequence support) and transmutation. This would be based on very similar layout requirements than placement but again the macro could annotate the type to guarantee them.

Okay, I have no idea what you are trying to get at here, and how it would solve/circumvent the issue of unclear safety requirements. Could you perhaps make a bit more concrete what your suggestion would be for this particular case?

Copy link

@197g 197g Feb 20, 2026

Choose a reason for hiding this comment

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

Transmuting requires moving it.

A literal transmute at the point of initialization of the value would indeed require moving, hadn't actually thought of it that way. I meant reinterpreting the type in-situ—hence requiring layout compatibility–as a clear semantics of the pointer while the place it is pointing to is (partially-)not-initialized (e.g. for the purposes of drop-glue in partly initialized state we could actually move but probably still never want to to avoid unsound Pin interactions). Maybe we could have Ptr<MaybeUninit<T>> be a value for creating an initially uninit place through a transmute if that solves part of the init-expression interactions.

Could you perhaps make a bit more concrete what your suggestion would be for this particular case?

#[derive(Place)]
struct MyKindOfBox<T> {
    alloc: NonNull<T>,
    // other fields not mentioning T
}

// Compiler generated:
unsafe impl<T> Place for MyKindOfBox<T> {
  // unstable details
  unsafe fn deref_ptr(*mut Self) -> *mut T {
      // Really probably purely synthesized MIR.
      unsafe { PlaceOrBase::deref_ptr(addr_of!(*self.alloc)) }
  }
}

// Private trait to define what fields can be used to derive
unsafe trait PlaceOrBase {}
impl<T: ?Sized+Place> PlaceOrBase for T {}
impl<T> PlaceOrBase for NonNull<T> {}

The semantics of Place would only need to be explained for the types supported by derive for now which seems much simpler since we can choose that restrictively. All the ambiguity of panicking MIR, the exact safety predicate for trait and deref, taking pointer or reference, can be figured out one by one and separately. Meanwhile we would still get to stably use UniqueArc-places etc. as users.

As for the InplaceBox construction above, it would depend on having a field recognized by the compiler to implement the trait from; MaybeUninit does not work for this purpose (as observed by example). I want to note that the example does not follow the usual pointer initialization (e.g. Box::assume_init) where the pointee has the MaybeUninit. ManuallyDrop is closer to the semantics that Box has for its place. The corresponding sequence of initialization is that we first construct a StackBox<MaybeUninit<T>> on top of an allocation (that allocation has type MaybeUninit<T>), then fill it with the value, and that turns it into StackBox<T>. The latter can only be overwritten with T so it can not be de-initialized as in the example.

It would solve the example in that we would never have to write a bogus &MaybeUninit<T> -> &T conversion guarantee. We have to discard the idea of initializing InplaceBox in a single line of code though. That seems a reasonable price to pay. As for how this could be treated more ergonomically refer to the paragraph above but macros would paper over ergonomics for a lot of cases I believe.

In the above context, the contents is also considered uniitialized if the whole or parts of the value of the contents has been moved out, or a destructor has been called upon them.

Dereferences of a type implementing `Place` can therefore be lowered directly to MIR, only
being elaborated in a pass after borrow checking. This allows the borrow checker to fully
check that the moves of data into and out of the type are valid.

The dereferences and drops of the contained value can then be elaborated in the passes
after borrow checking. This process will be somewhat similar to what is already done for
Box, with the difference that dereferences of types implementing `Place` may panic. We
propose to handle these panics by aborting to avoid introducing interactions with drop
elaboration and new execution paths not checked by the borrow checker.

In order to generate the function calls to the `Place::place` and `Deref::deref` during
the dereference elaboration we propose making these functions additional language items.

## Drawbacks
[drawbacks]: #drawbacks

There are three main drawbacks to the design as outlined above. First, the traits are
unsafe and come with quite an extensive list of requirements on the implementing type.
This makes them relatively tricky and risky to implement, as breaking the requirements
could result in undefined behavior that is difficult to find.

Second, with the current design the underlying type is no longer aware of whether or not
the space it has allocated for the value is populated or not. This inhibits functionality
which would use this information on drop to automate removal from a container. Note
however that such usecases can use a workaround with the user explicitly requesting
removal before being able to move out of a smart-pointer like type.

Finally, the type does not have runtime-awareness of when the value is exactly added.
This means that the proposed traits are not suitable for providing transparent locking of
shared variables to end user code.

In past proposals for similar traits it has also been noted that AutoDeref is complicated
and poorly understood by most users. It could therefore be considered problematic that
AutoDeref behavior is extended. However the behavior here is identical to what Box already
has, which is considered acceptable in its current state.

## Rationale and alternatives
[rationale-and-alternatives]: #rationale-and-alternatives

Ideas for something like the `Place` trait design here can be found in past discussions of
DerefMove traits and move references. The desire for some way of doing move derefences
goes back to at least https://github.com/rust-lang/rfcs/issues/997.

The rationale behind the current design is that it explicitly sticks very closely to what
is already implemented for Boxes, which in turn closely mirror what can be done with stack
variables directly. This provides a relatively straightforward mental model for the user,
and significantly reduces the risk that the proposed design runs into issues in the
implementation phase.

### DerefMove trait

Designs based on a simpler DerefMove trait have been previously proposed in the unmerged
[RFC2439](https://github.com/rust-lang/rfcs/pull/2439) and an [internals forum thread](https://internals.rust-lang.org/t/derefmove-without-move-why-dont-we-have-it/19701).
These come down to a trait of the form
```
trait DerefMove : DerefMut {
fn deref_move(self) -> Self::Target
}
```

The disadvantage of an approach like this is that it is somewhat unclear how to deal with
partial moves. This has in the past stopped such proposals in their tracks.

Furthermore, such a trait does not by itself cover the entirety of the functionality
offered by Box, and given its consuming nature it is unclear how to extend it. This also
leads to the potential for backwards incompatible changes to the current behavior of Box,
as has previously been identified.

### &move based solutions

A separate class of solutions has been proposed based on the idea of adding &move
references to the type system, where the reference owns the value, but not the allocation
behind the value. These were discussed in an [unsubmitted RFC by arielb1](https://github.com/arielb1/rfcs/blob/missing-derefs/text/0000-missing-derefs.md),
and an [internals forum thread](https://internals.rust-lang.org/t/pre-rfc-move-references/14511)

Drawbacks of this approach have been indicated as the significant extra complexity which
is added to the type system with the extra type of reference. There further seems to be a
need for subtypes of move references to ensure values are moved in or out before dropping
to properly keep the allocation initialized or deinitialized after use as needed.

This additional complexity leads to a lot more moving parts in this approach, which
although the result has the potential to allow a bit more flexibility makes them less
attractive on the whole.

### More complicated place traits

Several more complicated `Place` traits have been proposed by tema2 in two threads on the
internals forum:
- [DerefMove without `&move` refs](https://internals.rust-lang.org/t/derefmove-without-move-refs/17575)
- [`DerefMove` as two separate traits](https://internals.rust-lang.org/t/derefmove-as-two-separate-traits/16031)

These traits aimed at providing more feedback with regards to the length of use of the
pointer returned by the `Place::place` method, and the status of the value in that
location after use. Such a design would open up more possible use cases, but at the cost
of significantly more complicated desugarings.

Furthermore, allowing actions based on whether a value is present or not in the place
would add additional complexity in understanding the control flow of the resulting binary.
This could make understanding uses of these traits significantly more difficult for end
users of types implement these traits.

### Limited macro based trait

Going the other way in terms of complexity, a `Place` trait with constraints on how the
projection to the actual location to be dereferenced was proposed in [another internals forum thread](https://internals.rust-lang.org/t/derefmove-without-move-references-aka-box-magic-for-user-types/19910).

This proposal effectively constrains the `Place::deref` method to only doing field
projections and other dereferences. The advantage of this is that such a trait has far
less severe safety implications, and by its nature cannot panic making its use more
predictable.

However, the restrictions require additional custom syntax for specifying the precise
process, which adds complexity to the language and makes the trait a bit of an outlier
compared to the `Deref` and `DerefMut` traits.

### Existing library based solutions

The [moveit library](https://docs.rs/moveit/latest/moveit/index.html) provides similar
functionality in its `DerefMove` trait. However, this requires unsafe code on the part of
the end user of the trait, which makes them unattractive for developers wanting the
memory safety guarantees rust provides.

## Prior art
[prior-art]: #prior-art

The behavior enabled by the trait proposed here is already implemented for the Box type,
which can be considered prime prior art. Experience with the Box type has shown that its
special behaviors have applications.

Beyond rust, there aren't really comparable features that map directly onto the proposal
here. C++ has the option for smart pointers through overloading dereferencing operators,
and implements move semantics through Move constructors and Move assignment operators.
However, moves in C++ require the moved-out of place to always remain containing a valid
value as there is no intrinsic language-level way of dealing with moved-out of places in
a special way.

In terms of implementability, a small experiment has been done implementing the deref
elaboration for an earlier version of this trait at
https://github.com/davidv1992/rust/tree/place-experiment. That implementation is
sufficiently far along to support running code using the Place trait, but does not yet
properly drop the internal value, instead leaking it.

## Unresolved questions
[unresolved-questions]: #unresolved-questions

The current design would require the use of `Deref::deref` in desugaring non-moving
accesses to types implementing `Place`. However, it is currently unclear whether it is
sound to do so if the value has already been partially moved out.

Right now, the design states that panic in calls to `Deref::deref` or `Place::place` can
cause an abort when the call was generated in the MIR. This is done as it is at this point
somewhat unclear how to handle proper unwinding at the call sites for these functions.
However, it may turn out to be possible to implement this with proper unwinding, in which
case we may want to consider handling panics at these call sites the same as for ordinary
code.

## Future possibilities
[future-possibilities]: #future-possibilities

Should the trait become stabilized, it may become interesting to implement non-copying
variants of the various pop functions on containers within the standard library. Such
functions could allow significant optimizations when used in combination with large
elements in the container.

It may also be interesting at a future point to reconsider whether the unsized_fn_params
trait should remain internal, in particular once Unsized coercions become usable with user
defined types. However, this decision can be delayed to a later date as sufficiently many
interesting use cases are already available without it.

Finally, there is potential for the trait as presented here to become useful in the in
place initialization project. It could be a building block for generalizing things like
partial initialization to smart pointers. This would require future design around an api
for telling the borrow checker about new empty values implementing Place, but that seems
orthogonal to the design here.