Custom references
Custom references refers to a bundle of features that allow user-defined types to emulate and extend the capabilities of Rust references:
Resources
HasPlace proposal
Note
This article is a stub. Help improve the wiki by clicking the edit icon above and submitting a pull request.
Resources
Truly First-Class Custom Smart Pointers | Nadri’s musings
Field projections
Note
This article is a stub. Help improve the wiki by clicking the edit icon above and submitting a pull request.
Resources
Project goal tracking issue, Fall 2025, contains many comprehensive updates
Design meeting 2025-08-13: Field projections - HackMD, Aug 2025
pre-RFC Field Projections v3 - HackMD, May 2025 or earlier
Reborrow
Reborrowing is an action performed on exclusive references which creates a copy of the source reference and marks it disabled for reads and writes. This retains the exclusivity of the reference despite creating a copy, as only one copy can be used at a time.
Today, true reborrowing is only available to Rust’s exclusive &mut T
references. Going beyond references means enabling true reborrowing for
user-defined exclusive reference types by defining a Reborrow trait.
We want to make it possible for both of the following functions to compile: an
exclusive &mut T reference and a user-defined custom exclusive reference
CustomMut<'_, u32> should have equivalent semantics.
Example:
#![allow(unused)]
fn main() {
fn f(a: &mut u32) {
f_x(a);
f_y(a);
}
fn g(a: CustomMut<'_, u32>) {
g_x(a);
g_y(a);
}
}
Use cases
Approaches
The current approach to reborrowing in user-land is based on an explicit method. The current work in the Reborrow traits lang experiment is based on a marker trait.
CoerceShared
Exclusive references call for a shared counterpart, into which an exclusive
reference can be coerced into. For Rust’s references, this is &T. For
user-defined exclusive reference types, a shared counterpart is a second
user-defined type that is freely shareable (read: is Copy). Coercing a
user-defined exclusive reference into a shared reference type requires defining
a CoerceShared trait.
Resources
Tracking Issue for Reborrow trait lang experiment · Issue #145612 · rust-lang/rust
Reborrow traits - Rust Project Goals, Jul 2025
rfcs/text/0000-autoreborrow-traits.md at autoreborrow-traits · aapoalas/rfcs, May 2025
Abusing reborrowing for fun, profit, and a safepoint garbage collector (conference talk with examples), Feb 2025
User-defined exclusive references
In some cases, users want to define their own custom reference types that have
equivalent semantics to Rust’s exclusive &mut T references but cannot, for
whatever reason, be directly expressible using them. For example, an exclusive
reference to unaligned data or an exclusive reference to a part of a matrix
could not be expressed using &mut T references. In other cases, the
exclusivity of the reference may not be a guarantee but more of a suggestion:
eg. for mutable C++ references it may be a good idea to try use them as
exclusive, but exclusivity is not guaranteed and thus using &mut T instead of
a custom type would cause undefined behaviour.
#![allow(unused)]
fn main() {
#[derive(Reborrow)]
struct CustomMut<'a, T>(*mut T, PhantomData<&'a mut ()>);
}
Wrapper types
Currently Rust does not automatically reborrow Option<&mut T> or similar
wrapper types. Conceptually, there is no reason why &mut T should be
reborrowable but Option<&mut T> should not: the only difference between the
two types is that one can also be null.
With the Reborrow trait, reborrowing wrapper types of exclusive references becomes possible using blanket implementations.
#![allow(unused)]
fn main() {
impl<T: Reborrow> for Option<T> { /* ... */ }
}
Marker types
Sometimes users want to define custom types with exclusive reference semantics that do not contain any pointers at all. This is useful in encoding exclusive access to data in the function’s indirect context. For example, embedded systems sometimes use reborrowable ZST marker types to pass exclusive access to hardware peripherals through their call stacks. The author uses a marker ZST to model garbage collection safepoints, ensuring that unrooted custom references to garbage collectable data in a system with a moving GC are not held past safepoints.
#![allow(unused)]
fn main() {
struct CustomMarker<'a>(PhantomData<&'a mut ()>);
}
Exclusive reference collections
In some cases it can be useful to group up multiple exclusive references into a single collection.
#![allow(unused)]
fn main() {
#[derive(Reborrow)]
struct MutCollection<'a, 'b, 'c> {
a: &'a mut A,
b: CustomMut<'b, B>,
c: Option<&'c mut C>,
}
}
Reborrowing such a collection as exclusive means simply reborrowing each exclusive reference individually and producing a new collection of the results. This can also be applied recursively:
#![allow(unused)]
fn main() {
#[derive(Reborrow)]
struct BiggerCollection<'a, 'b, 'c, 'd, 'e, 'f, 'g> {
one: MutCollection<'a, 'b, 'c>,
two: MutCollection<'d, 'e, 'f>,
three: CustomMut<'g, G>,
}
}
Marker trait approach
The current Reborrow traits lang experiment aims to produce a method-less
Reborrow trait. For exclusive reference semantics this is mostly trivial: the
concrete code generated for an exclusive reborrow is equivalent to that of
Copy, and only additional lifetime semantics must be considered on top.
This gives us a trivial, derivable Reborrow trait:
#![allow(unused)]
fn main() {
trait Reborrow {}
}
Its usage is performed through a derive macro:
#![allow(unused)]
fn main() {
#[derive(Reborrow)]
struct CustomMarker<'a>(PhantomData<&'a mut ()>);
}
There are some limitations that we wish to impose on types that derive (or implement) this trait.
- The type must not be
CloneorCopy.
- Alternatively, blanket-implement
Reborrow for T: Copy, butClone + !Copytypes cannot beReborrow. - This limitation is placed to avoid types that are both
CopyandReborrow, as that only makes sense ifCopytypes are considered a “base case” for recursive reborrowing. Clone + !Copytypes generally only make sense if a correspondingDropimplementation exists, and reborrowing cannot be soundly combined withDrop. If a type isClone + !Copy + !Dropthen technically it can be soundly reborrowed, but it’s unlikely that the type itself makes overmuch sense (it is by definition a cloneable, ie. shareable, type with exclusive reference semantics).
- The type must not have a
Dropimplementation.
- Reborrowable types can have multiple owned copies in existence, and each one
of these would need to call
Drop(as we cannot know if we have the only copy or not). This is effectively guaranteed to end up causing a double-free.
- The type must have at least one lifetime.
- Alternatively, if
Copytypes blanket-implementReborrowthen this limitation cannot be placed, but manually derivingReborrowon a lifetime-less type should be an error. - This limitation is placed simply because a lifetime-less type cannot contain the lifetime information that sound reborrowing relies upon.
- The result of a
Reborrowoperation is simply the type itself, including the same lifetime.
- Reborrowing should produce a copy, not eg. a field or a new struct consisting of a subset of the type’s fields.
- If
Reborrowreturns a lifetime that is always shorter than the source lifetime, then values deriving from the operation cannot be returned past it up the call stack. A longer lifetime is of course meaningless. Thus, the same lifetime is what we should get.
Derivation of the Reborrow operation
Since the marker trait approach has no explicit fn reborrow method, the
compiler must derive the correct operation for Reborrow. When exactly one
lifetime exists on the type, that derivation is trivial: it is simply a Copy
and an exclusive reborrow of the singular lifetime. Though, this too can be
questionable:
#![allow(unused)]
fn main() {
#[derive(Reborrow)]
struct Bad<'a>(&'a ());
}
Is the above type actually an exclusive reference? Is deriving Reborrow on it
an error? I think it should be, but it’s a little hard to exactly say why:
arguably it’s because reborrowing (re)asserts exclusive access while this type,
by definition, does not have exclusive access to anywhere. But maybe this is
created using a &mut () in which case it sort of does carry exclusive access,
it just cannot re-assert it.
If there are multiple lifetimes, then deriving Reborrow becomes more
complicated. Consider the following type:
#![allow(unused)]
fn main() {
#[derive(Reborrow)]
struct Two<'a, 'b>(&'a (), &'b mut ());
}
This type should only exclusively reborrow the second reference.
These questions become a step more complicated once we give up on using Rust references and go into the world of custom types:
#![allow(unused)]
fn main() {
#[derive(Reborrow)]
struct MaybeBad<'a>(PhantomData<&'a ()>);
}
This type may or may not be bad: we simply have no idea. Whether the borrow
inside of PhantomData is &mut () or &() has no effect on whether or not
this type carries an “exclusive lifetime” or not.
This issue seems to call for a new kind of marker type:
#![allow(unused)]
fn main() {
/// Marker type for exclusive reference semantics.
#[derive(Reborrow)]
struct PhantomExclusive<'a>; // explicitly no content; this is purposefully bivariant.
impl<'a> PhantomExclusive<'a> {
/// Capture an exclusive reference into a PhantomExclusive.
fn from_mut<T>(_: &'a mut T) -> Self {
Self
}
/// Create a new unbound PhantomExclusive.
///
/// # Safety
///
/// * The caller must ensure that only one PhantomExclusive is created for
/// whatever that they're tracking.
unsafe new() -> Self {
Self
}
}
}
The compiler would track PhantomExclusive as an exclusive reference without
the pointer bits. Our custom type would then be:
#![allow(unused)]
fn main() {
#[derive(Reborrow)]
struct Good<'a>(PhantomData<&'a ()>, PhantomExclusive<'a>);
}
The first PhantomData is there to establish variance while the
PhantomExclusive is included to ensure 'a is an “exclusive lifetime”.
Recursive nature of deriving Reborrow
The derivation of Reborrow (and CoerceShared) has a recursive nature: we can
group up individual exclusive references (be they &mut T or CustomMut<'_, T>
or CustomMarker<'_>) into a single struct and derive Reborrow on it. This
derivation is done on the fields of the type, and when a field is found to be
Reborrow then that field’s reborrow operation becomes a part of the larger
whole’s operation.
This has some complexities regarding what are the “bottom/base cases” of the recursion.
&'a mut Tbottoms out and performs a reborrow on'aCustomMut<'a, T>can be assumed to bottom out and reborrow on'a.Two<'a, 'b>needs to be checked: does it reborrow both'aand'bor only one of them?PhantomData<&'a ()>bottoms out and DOES NOT perform a reborrow on'a: this is aCopymarker.PhantomExclusive<'a>bottoms out and performs a reborrow on'a.
Method-based approach
Today, exclusive references can be implemented in user-land using a method-based approach:
#![allow(unused)]
fn main() {
trait Reborrow {
fn reborrow(&mut self) -> Self;
}
}
This captures the most important features of reborrowing: a source instance
self has exclusive access asserted on it, and a new Self is produced from it
(Some formalisations allow the method to choose its own result type using a
Self::Target ADT: this is arguably mixing up reborrowing with a generalised
Deref operation). However, this approach comes with downsides: the method
requires an explicit &mut self reference which bounds the resulting Self’s
lifetime to the calling function, and the method is user-overridable which leads
to arguably non-idiomatic “spooky action” and a possibility of misuse.
Bounded lifetimes
When the fn reborrow method is called in some outer function fn outer_fn,
The outer function must create a &mut T reference pointing to the value being
reborrowed:
#![allow(unused)]
fn main() {
fn outer_fn<'a>(t: CustomMut<'a, u32>) -> &'a u32 {
// This:
inner_fn(t.reborrow());
// ... is equivalent to this:
let t_mut = &mut t;
inner_fn(t_mut.reborrow())
}
}
This means that the fn reborrow method is given a reference pointing to a
local value, effectively a pointer onto the stack. The compiler must make sure
that this pointer does not outlive the stack, which then means that the lifetime
of the resulting Self created by fn reborrow cannot outlive the function in
which it was created in. In the above example, this means that trying to return
the result of inner_fn will not compile because of the fn reborrow call,
citing “returns a value referencing data owned by the current function”.
Compare this to Rust references: the compiler understands that the result of
reborrowing a &mut produces a new reference that can be extended to the
original reference’s lifetime. This function compiles despite an explicit
reborrow being performed by the &mut *t code.
#![allow(unused)]
fn main() {
fn outer_fn<'a>(t: &'a mut u32) -> &'a u32 {
inner_fn(&mut *t)
}
}
We can make the code not compile by explicitly creating a &mut t reference:
#![allow(unused)]
fn main() {
fn outer_fn<'a>(mut t: &'a mut u32) -> &'a u32 {
// no longer compiles: returns a value referencing data owned by the current
// function
inner_fn(&mut t)
}
}
In user-land code bases that use the explicit fn reborrow method, there exists
a way to fix this issue: by simply removing the fn reborrow method call, the
original example code will compile. But knowing of this fix requires some deeper
understanding of the fn reborrow method and the borrow checker: it is not an
obvious or clean fix.
Most importantly, if the Rust compiler is in charge of automatically injecting
fn reborrow calls at appropriate use-sites, then it may not be feasible for
the compiler to perform the analysis to determine if a particular call should be
removed or not. Furthermore, in a post-Polonius borrow checker world it will
become possible for code like this to compile:
#![allow(unused)]
fn main() {
fn inner_fn<'a>(t: &'a mut u32) -> Result<&'a u32, &'a u32> {
// ...
}
fn outer_fn<'a>(t: &'a mut u32) -> Result<&'a u32, &'a u32> {
let result = inner_fn(t)?;
if result > 100 {
Ok(t)
} else {
Err(t)
}
}
}
This means that a reborrow of t must always happen, yet the lifetime expansion
of the result of inner_fn depends on whether the function returns Ok or
Err. In the Err branch the result’s lifetime must expand to that of the
source t, but in the Ok branch it must shrink to re-enable usage of the
source t. The method-based approach to Reborrow traits will not work here, as
the compiler cannot choose to call the method based on a result value that
depends on the call’s result.
One could argue that the compiler could simply extend the lifetime of the method call’s result, as it should only do good deeds. This may well open a soundness hole that allows safe Rust code to perform use-after-free with stack references.
User-controlled code
The second downside of the method-based approach is that reborrowing is fully invisible in the source code, and having user-controlled code appear where there is none visible is not very idiomatic Rust.
Consider eg. the following code:
struct Bad;
impl Reborrow for Bad {
fn reborrow(&mut self) -> Self {
println!("I'm bad!");
Self
}
}
fn main() {
let bad = Bad;
let bad = bad;
}
Depending on the exact implementation choices, this might print out the “I’m
bad” message. This is especially problematic if the compiler chooses to trust
that fn reborrow methods never store their &mut self reference given to them
and allows lifetime extension to happen:
#![allow(unused)]
fn main() {
struct Unsound<'a>(&'a mut &'a u32, u32);
// Note: lifetime for trait here to show that the compiler specially trusts that
// these lifetimes indeed are fully unifiable.
impl<'a> Reborrow<'a> for Unsound<'a> {
fn reborrow(&'a mut self) -> Self {
let data = &self.1;
self.0 = data;
Self(self.0, self.1)
}
}
}
This would absolutely not compile today, but if the compiler did truly trust that reborrow methods can do no wrong then something like this might just pass through the compiler’s intentional blindspot and become a soundness hole.
CoerceShared
Note
This article is a stub. Help improve the wiki by clicking the edit icon above and submitting a pull request.
If exclusive references exist, then shared references are nearly always
necessary as well. Rust’s own exclusive &mut T references automatically coerce
into shared &T references as necessary, and we want to enable this same
coercion for custom user-defined exclusive references as well. For this purpose
we define a CoerceShared trait.
Use cases
To be fleshed out. All the same cases apply as for Reborrow.
Note that some custom reference cases might carry extra metadata (eg. a usize)
for exclusive references which then gets dropped out when coercing to shared.
Approaches
The current approach to shared coercion in user-land is based on an explicit method. The current work in the Reborrow traits lang experiment is based on a marker trait.
Marker trait approach
Coercing a custom user-defined exclusive reference into a user-defined shared
reference is a slightly involved affair: the two types are obviously going to be
different types, just like &mut T and &T are different, but they must also
be similar enough that a pure marker trait can relate the types to one another.
From a user’s perspective, coercing an exclusive reference into shared is a simple operation: just name the source exclusive reference type and the target shared reference type.
This gives us the following trait definition:
#![allow(unused)]
fn main() {
trait CoerceShared<Target: Copy>: Reborrow {}
}
Its usage is done either manually through an impl Trait statement, or possibly
by deriving CoerceShared on the source exclusive reference type:
#![allow(unused)]
fn main() {
// impl Trait statement
#[derive(Reborrow)]
struct CustomMarker<'a>(PhantomData<&'a mut ()>);
struct CustomMarkerRef<'a>(PhantomData<&'a ()>);
impl<'a> CoerceShared<CustomMarkerRef<'a>> for CustomMarker<'a> {}
// derive macro
#[derive(Reborrow, CoerceShared(CustomMarkerRef))]
struct CustomMarker<'a>(PhantomData<&'a mut ()>);
struct CustomMarkerRef<'a>(PhantomData<&'a ()>);
}
As with the Reborrow marker trait, some limitations are placed on the trait although this time most of them are expressed on the trait directly.
- The type implementing
CoerceSharedmust also implementReborrow.
- Coercing an exclusive reference into a shared reference doesn’t make sense if the source type is not an exclusive reference.
- The result of the
CoerceSharedoperation must be aCopytype.
CoerceSharedcan be performed any number of times on the same value, always producing a byte-for-byte copy (ignoring any padding bytes). These results are thus copies of one another, so it must also be possible to performCoerceSharedonce and produce copies of that result.
- The result of the
CoerceSharedoperation must have at least one lifetime.
- A lifetime-less type cannot contain the lifetime information that sound reborrowing relies upon.
- The lifetime of the result must be equivalent to the source.
- If
CoerceSharedreturns a lifetime that is always shorter than the source lifetime, then values deriving from the operation cannot be returned past it up the call stack. A longer lifetime is of course meaningless. Thus, the same lifetime is what we should get.
- The target type must be relatable to the source type field-by-field.
- In order for the marker trait to be derivable by the compiler, its contents must be dericable from the source and target types. This is most reasonably performed field-by-field.
For exclusive reference types that have at most one data field and exactly one
lifetime, coercing into a shared reference type that has the same data field and
exactly one lifetime, the derivation of CoerceShared is trivial. For types
that have multiple fields and/or multiple lifetimes, the derivation becomes more
complicated.
Method-based approach
Today, coercing exclusive references to shared references can be implemented in user-land using a method-based approach:
#![allow(unused)]
fn main() {
trait CoerceShared {
type Target: Copy;
fn coerce_shared(&self) -> Self::Target;
}
}
This approach suffers from the downsides as
method-based Reborrow does. In addition, it is not
possible to fix the lifetime issues by simply not calling the fn coerce_shared
method as that would mean trying to use a Self type where Self::Target is
required.
The way to fix this is to define an Into<Self::Target> method that consumes
the source exclusive reference and produces a shared reference with the same
lifetime as a result. Then, instead of calling the fn coerce_shared method the
fn into method is called instead.
Associated type or type argument
In general, there is no reason for why an associated type would be preferable
versus a type argument in the CoerceShared trait: especially with exclusive
reference collections it might make sense for a single reborrowable type to have
multiple CoerceShared targets. If the compiler automatically injects the
correct fn coerce_shared method call, then an associated type becomes
preferable.
The problem is that if the requirements for implementing CoerceShared are not
strict enough and a type argument is used, then the trait could become a vehicle
for generic automatic value coercion. For example:
#![allow(unused)]
fn main() {
struct Int(i64);
impl CoerceShared<i64> for Int {
fn coerce_shared(&self) -> i64 {
self.0
}
}
impl CoerceShared<u64> for Int {
fn coerce_shared(&self) -> u64 {
self.0 as u64
}
}
impl CoerceShared<i32> for Int {
fn coerce_shared(&self) -> i32 {
self.0 as i32
}
}
impl CoerceShared<u32> for Int {
fn coerce_shared(&self) -> u32 {
self.0 as u32
}
}
// ... and so on ...
}
Receiver
Note
This article is a stub. Help improve the wiki by clicking the edit icon above and submitting a pull request.
The Receiver trait enables “arbitrary self types” by doing two things:
- Defining when a smart pointer type is allowed to be a method receiver.
- Generalizing method receivers past types that implement
Deref.
Resources
3519-arbitrary-self-types-v2 - The Rust RFC Book
- Tracking issue for RFC 3519:
arbitrary_self_types· Issue #44874 · rust-lang/rust - Arbitrary self types v2: stabilize by adetaylor · Pull Request #135881 · rust-lang/rust
Autoref
Note
This article is a stub. Help improve the wiki by clicking the edit icon above and submitting a pull request.
Resources
Autoref and Autoderef for First-Class Smart Pointers | Nadri’s musings, Dec 2025
- issue comment with design sketch (see “Summary sketch”)
In-place initialization
Initializing values in-place without copying them. This eliminates unnecessary copies and allows for self-referential datastructures.
Range of use cases
TODO: Cover the range of use cases like
- Pinned vs unpinned
- Constructor functions
- Fallible
Approaches
Potential design axioms
TODO: Add more or remove.
- Support abstraction through function boundaries
Resources
In-place initialization - Rust Project Goals, Fall 2025
#t-lang/in-place-init > in-place initialization: RfL design wishes - rust-lang - Zulip
Init expressions
Note
This article is a stub. Help improve the wiki by clicking the edit icon above and submitting a pull request.
Resources
Init expressions / In-place initialization, Jun 7 2025
Out pointers
Note
This article is a stub. Help improve the wiki by clicking the edit icon above and submitting a pull request.
Resources
In-place initialization via outptrs, Jul 8 2025
- Introduced
InPlace<T>as aBoxwith custom allocator. - Very influential.
Thoughts on “out”-pointer, Nov 12 2025
Placing functions
Note
This article is a stub. Help improve the wiki by clicking the edit icon above and submitting a pull request.
Resources
placing functions, July 2025
Placing Arguments, August 2025
Guaranteed value emplacement
Note
This article is a stub. Help improve the wiki by clicking the edit icon above and submitting a pull request.