Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

#t-lang/custom-refs

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

#t-lang/custom-refs

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.

  1. The type must not be Clone or Copy.
  • Alternatively, blanket-implement Reborrow for T: Copy, but Clone + !Copy types cannot be Reborrow.
  • This limitation is placed to avoid types that are both Copy and Reborrow, as that only makes sense if Copy types are considered a “base case” for recursive reborrowing.
  • Clone + !Copy types generally only make sense if a corresponding Drop implementation exists, and reborrowing cannot be soundly combined with Drop. If a type is Clone + !Copy + !Drop then 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).
  1. The type must not have a Drop implementation.
  • 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.
  1. The type must have at least one lifetime.
  • Alternatively, if Copy types blanket-implement Reborrow then this limitation cannot be placed, but manually deriving Reborrow on 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.
  1. The result of a Reborrow operation 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 Reborrow returns 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 T bottoms out and performs a reborrow on 'a
  • CustomMut<'a, T> can be assumed to bottom out and reborrow on 'a.
  • Two<'a, 'b> needs to be checked: does it reborrow both 'a and 'b or only one of them?
  • PhantomData<&'a ()> bottoms out and DOES NOT perform a reborrow on 'a: this is a Copy marker.
  • 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.

  1. The type implementing CoerceShared must also implement Reborrow.
  • Coercing an exclusive reference into a shared reference doesn’t make sense if the source type is not an exclusive reference.
  1. The result of the CoerceShared operation must be a Copy type.
  • CoerceShared can 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 perform CoerceShared once and produce copies of that result.
  1. The result of the CoerceShared operation must have at least one lifetime.
  • A lifetime-less type cannot contain the lifetime information that sound reborrowing relies upon.
  1. The lifetime of the result must be equivalent to the source.
  • If CoerceShared returns 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.
  1. 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

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

Ensure arbitrary_self_types method resolution is forward-compatible with custom autoref · Issue #136987, Feb 2025

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

#t-lang/in-place-init

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 a Box with 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.

Resources

RFC Draft: Guaranteed Value Emplacement - HackMD