---
title: "Design meeting 2026-04-01: Field Projections via Virtual Places"
tags: ["T-lang", "design-meeting", "minutes"]
date: 2026-04-01
discussion: https://rust-lang.zulipchat.com/#narrow/channel/410673-t-lang.2Fmeetings/topic/Design.20meeting.202026-04-01.3A.20Field.20Proj.20via.20Virtual.20Places
url: https://hackmd.io/H5d2-83ER2ymNPZVIWCYWg
---
> [!NOTE]
> Thanks to Benno, Nadrieril, and Tyler for this document.
# Design Meeting 2026-04-01: Field Projections via Virtual Places
## Purpose of this Meeting
We (Tyler, Nadrieril, Benno) have been hard at work on this feature for a number of months as a close-knit working-group. It's time to get more eyes on our proposal.
Since the [last design meeting](https://hackmd.io/@rust-lang-team/S1I1aEc_lx) in August 2025, our proposal has changed fundamentally. The motivation section and the design axiom sections from that meeting still apply today.
What we'd like to get out of this meeting:
- Socialize the core ideas of the proposal, get y'all excited, and potentially obtain more use-cases;
- Validate our holistic view from an end-user's perspective of custom pointer features & their ergonomics;
- Surface any crucial concerns of the design; "do we have permission to continue in this direction?".
Explicitly out of scope, to stay focused:
- Exact safety documentation (safety requirements and safety justification) of traits, operations, and implementations.
- The shape of `Projection` types and how to inspect them.
- How we handle unsized types and metadata of pointers.
- Syntax and name bikeshedding.
- Backwards compatibility and paths to stabilization.
- How borrow checking is going to work in detail.
We're happy to chat about these asynchronously (either in the discussion section or on Zulip later).
## Introduction
### Custom Pointers
Rust has expressive smart pointers, however they are not fully integrated into the language. Most smart pointers are well-served by `Deref[Mut]`, but raw pointers and their wrappers (such as `NonNull<T>`) have very bad ergonomics. In addition, references and `Box` have compiler-builtin features improving their ergonomics that are not available for user-defined pointers.
At the same time, the Rust ecosystem has a growing need for ergonomic user-definable pointers:
- Rust for Linux recently (see [this patch series](https://lore.kernel.org/rust-for-linux/20260302164239.284084-1-gary@kernel.org/)) added [infrastructure for field projections](https://origin.kernel.org/doc/rustdoc/next/kernel/ptr/index.html) to support pointers to DMA memory & more.
- The standard library [wishes to add `ArcRef<T>`](https://github.com/rust-lang/libs-team/issues/700#issuecomment-4032642473); additionally, the projection functions [`cell::RefMut::map`](https://doc.rust-lang.org/core/cell/struct.RefMut.html#method.map) and [`cell::RefMut::map_split`](https://doc.rust-lang.org/core/cell/struct.RefMut.html#method.map_split) already exist.
- The `pyref` crate [plans to add `PyRef[Mut]::map`](https://github.com/PyO3/PyO3/issues/2300).
There are *a lot* of pointers that would benefit from better ergonomics. To give a sense of the vastness and variety, here is a sizeable selection:
- "Dumb" pointers: `*mut T`, `*const T`, `NonNull<T>`, `VolatilePtr<T>`,`DmaPtr<T>`, `AtomicPtr<T>`, `WrappingPtr<T>`, `CppRef[Mut]<T>`
- References: `&T`, `&mut T`, `&own T`, `&uninit T`
- Guard References: `cell::Ref[Mut]<'_, T>`, `SeqLockRef<'_, T>`, `MutexGuard<'_, T>`
- Pin: `Pin<P>`
- Smart pointers: `Box<T>`, `Arc<T>`, `Rc<T>`, `ArcRef<T>`, `ArcBorrow<'_, T>`, `UniqueArc<T>`, `Py<T>`, `PyRef<'_, T>`, `Gc<T>`
Our approach for virtual places provides an elegant solution for all of the pointers listed here. Our design has been guided by the following design axiom:
> [!Important]
> **Using custom (dumb & smart) pointers should *feel* the same way as using builtin references.**
### Wrapper Types
Another user-definable aspect of Rust that naturally interacts with custom pointers are *wrapper types* (aka *container types*): `MaybeUninit<T>`, `Cell<T>`, `UnsafeCell<T>`, `ManuallyDrop<T>`, `Untrusted<T>`, `Mutex<T>`. These wrappers have various properties that transitively affect all fields of the wrapped type. For example, a `MaybeUninit<Struct>` essentially is like `Struct` where each field has the type `MaybeUninit<Field>` instead of `Field`.
Wrapper types are often combined with a pointer to form a new pointer, for example `&mut MaybeUninit<Struct>`; this allows precise separation of concerns, `&mut T` does not have to consider uninitialized memory, while `MaybeUninit<T>` doesn't have to be known to the borrow checker. These composed pointers also are not sufficiently integrated into the language. For example, `r: &mut MaybeUninit<Struct>` does not have the useful property of being able to access fields with `&mut r.field` like `&mut Struct` does.
Our proposal offers native support for wrapper types, guided by another design axiom:
> [!Important]
> **Combining custom wrapper types with custom pointers should result in a pointer that *feels* like a builtin reference.**
### Rationale for a Language Feature
Libraries can achieve a lot with macros; and certain new language features such as reflection could make library implementations of field projections more powerful. However, they can never reach the level of *ergonomics* that a dedicated language feature for field projections offers. Crucially they also cannot provide tighter borrow checker integration.
### Prerequisites
To understand this document, readers should be familiar with the notion of places in Rust. Nadrieril has a good [blog post](https://nadrieril.github.io/blog/2025/12/06/on-places-and-their-magic.html) on this topic that defines several terms that we expect readers to know about.
In addition, this document is written for consumption by language experts; we did not dwell on already-known details of the language. We plan to update our wiki with a more approachable document of the details of this approach in the near future.
## The Case for a Solution Based on Places
In this section, we explore several usage patterns of pointers that we think should exist in the language. They guide our design and give rationale for the most important decisions in our proposal. Note that we do not present any details of our proposal in this section.
### `Deref` is insufficient
Assume a smart pointer `MyPtr` that implements `Deref` and `DerefMut`. The following causes a borrowck error:
```rust
let mut x: MyPtr<Foo> = MyPtr::new(...);
let a = &mut x.a;
let b = &mut x.b; // ERROR: cannot borrow `x` as mutable more than once at a time
*a = 0;
*b = 0;
```
This code is allowed if instead of `MyPtr<Foo>` we have `&mut Foo` or `Box<Foo>`, because these have special support in the borrow-checker. We want this for custom pointers too.
The desugaring shows why the error happens: the second call to `deref_mut` invalidates `a`:
```rust
let mut x: MyPtr<Foo> = MyPtr::new(...);
let deref_x: &mut Foo = (&mut x).deref_mut();
let a = &mut (*deref_x).a;
let deref_x: &mut Foo = (&mut x).deref_mut(); // ERROR: cannot borrow `x` as mutable more than once at a time
let b = &mut (*deref_x).b;
*a = 0;
*b = 0;
```
**Conclusion:** the meaning of `&mut x.a` must change to only operate on the subplace `x.a` and not the entire place `x`.
### Autoref
To be really first-class, autoref on subfields should "Just Work":
```rust
impl Bar {
fn takes_py_ref_mut(self: PyRefMut<'_, Self>) { ... }
}
let x: PyRefMut<'_, Foo> = ...;
x.bar.takes_py_ref_mut();
x.bar2.takes_py_ref_mut();
```
So we need a way to go from `PyRefMut<'_, Foo>` to `PyRefMut<'_, Bar>` -- this is the core idea of field projections. Furthermore, we want to be able to also call methods with compatible receivers:
```rust
impl Bar {
fn takes_shared(&self) { ... }
fn takes_mut(&mut self) { ... }
fn takes_py_ref(self: PyRef<'_, Self>) { ... }
fn takes_py_ref_mut(self: PyRefMut<'_, Self>) { ... }
}
let x: PyRefMut<'_, Foo> = ...;
x.bar.takes_shared();
x.bar.takes_mut();
x.bar.takes_py_ref();
x.bar.takes_py_ref_mut();
```
To make this work, the projection operation needs to support:
- changing the output pointer kind entirely, and
- passing information (for `PyRef`/`PyRefMut` a `Py` token, or for `Arc`/`ArcRef` a pointer to the refcount) from the source pointer to the target pointer.
This is a many-to-many relationship: every pair of (source pointer, target pointer) needs special code to make it work; in addition, the borrow checker needs to treat the operation differently depending on both the source and the target.
**Conclusion:** a pointer should be able to opt-in to be projected into several other pointers. The knowledge of the source and target pointers types should be enough to infer which operation is performed.
### Projecting one field at a time is tricky
The core idea of the Field Projections initiative (going from a `MyPtr<Foo>` to a `MyPtr<Field>` that points to a field of the original `Foo`) raises an important question: can we project one field at a time? I.e. can `project!(x, field.a)` be implemented as `project!(project!(x, field), a)`?
The answer is **no**: we already run into problems when desugaring `&mut` to field-by-field:
```rust
let foo: &mut Foo = ...;
let a: &mut A = &mut foo.bar.a;
*a = ...;
let b: &mut B = &mut foo.bar.b;
*a = ...;
*b = ...;
// With field-by-field, desugars to:
let bar = &mut foo.bar;
let a: &mut A = &mut bar.a;
*a = ...;
let bar = &mut foo.bar; // ERROR: cannot mutably borrow `foo.bar` twice
let b: &mut B = &mut bar.b;
*a = ...; // INFO: first borrow later used here
*b = ...;
```
One could try to run borrow-checking before desugaring, but that runs into the issue that we still create aliasing mutable references:
```rust
let foo: &mut Foo = ...;
let a: &mut A = &mut foo.bar.a;
*a = ...; // this write activates the pointer `a`
let b: &mut B = &mut foo.bar.b; // the intermediate borrow to `foo.bar` invalidates `a`
*a = ...; // UB
*b = ...;
```
So field-by-field projections for `&mut` cause UB under the currently proposed aliasing models[^1].
[^1]: See [this discussion](https://rust-lang.zulipchat.com/#narrow/channel/522311-t-lang.2Fcustom-refs/topic/Multi-level.20projections/near/555233842) for more details.
**Conclusion:** projections must be done all at once; no intermediate pointer values should be created when composing projections.
### Compositionality
While we musn't create intermediate pointer values, the *syntax* should still be compositional. `x.a.b` should be the same as `(x.a).b`, whenever the individual components make sense.
This already has a solution in Rust: place expressions. `x`, `*x`, `x.a` and `x.a.b` are expressions that denote a place.
The various parts at play are: if `x: &mut T` and `fn foo` takes `&self`, `x.a.foo()` actually stands for `Foo::foo(&(*x).a)`. Here `*x` is a place expression of type `T`, and `(*x).a` is a place expression of type `Foo`. Then we borrow the place with `&<place>` to make a new pointer to it.
Places are also the language of the borrow-checker; they're exactly what we need in order to reason about aliasing and moves.
**Conclusion:** places are a powerful concept and something users are familiar with; we should make use of it.
### Summary
We propose that custom pointers should be based on places: if `x: MyPtr<T>`, then `*x` is a place expression of type `T`. To specify which type `MyPtr` points to, we propose the `HasPlace` trait: if `x` is of type `X` and `X: HasPlace`, then `*x` is allowed and is a place expression of type `X::Target`.
```rust
trait HasPlace {
type Target: ?Sized;
}
```
In today's Rust there are a few operations we can perform on a place:
- read from it: `let x = <place>;`,
- write to it: `<place> = something();`,
- borrow it: `&<place>`, `&mut`, `&raw const`, `&raw mut`,
- read its discriminant: `match <place> { Some(_) => ..., None => ..., }`.
We propose that each of these operations should be governed by a different trait, in the same way that `+`, `-`, etc have `Add` and `Sub`. Each operation trait is opt-in, allowing for maximum flexibility. For example, we can define a pointer that doesn't correspond to a region in memory -- we call these "virtual places".
Moreover, each pointer "becomes" its own kind of borrow: the syntax `@MyPtr <place>` borrows `<place>` using the `MyPtr` pointer. The operation trait corresponding to place borrowing is capable of representing the many-to-many relationship of borrowing. So one could write e.g. `@NonNull x.a.b` where `x: NonNull<T>`, `x: *mut T`, or even `x: &NonNull<T>`. This borrowing operation is also used by autoref: `x.a.b.foo()` is desugared to `Foo::foo(@MyPtr (*x).a.b)`.
We will detail how we imagine these traits to look like in the "Details" section.
## A Survey of Custom Pointer Features
In this section, we explore the user-facing features of pointers in detail. We also show our vision of syntax and ergonomics of using pointers that have those features, which will inform the detailed design in the next section.
### `Box<T>` -- Reading, Writing, Moving Out, and Dropping
All of the following operations exist for `Box<T>` today:
```rust
let mut bx = Box::new(42);
// We can copy the pointee, if it is `Copy`:
let value = *bx;
// We can overwrite the pointee:
*bx = 24;
// We can do the same with subplaces.
let mut bx = Box::new(Struct {
greeting: String::from("hello"),
greetee: String::from("world"),
foo: Foo::new(),
});
// We can move parts of the pointee out:
let field = bx.greeting;
// We can move parts back in:
bx.greeting = String::from("hi");
// We can also handle chained subplaces:
let bar: Bar = bx.foo.bar;
// And even indexing into an array/slice:
let baz: Baz = bx.foo.bazes[42];
// If we move out a `!Copy` field...
let greetee = bx.greetee;
// ...then dropping `bx` here results in:
// - only a drop to `greeting` and `foo` (since `greetee` was moved out above)
// - and then a dealloc of the box
```
We also want to allow implementing these operations for any custom type.
### `ArcRef<T>` -- Custom-ptr borrowing & Autoref
The `@MyPtr <place>` syntax provides custom borrowing:
```rust
let arc = Arc::new(Struct {
field: Field { nested: String::from("Hello") },
});
// Borrowing a field of a struct in an `Arc` (already works today):
let field: &Field = &arc.field;
// Custom borrow of a field in an `Arc`:
let field: ArcRef<Field> = @ArcRef arc.field;
// We can also borrow the entire contents:
let full: ArcRef<Struct> = @ArcRef *arc;
// Ergonomics could be improved with "canonical" borrowing:
let other: ArcRef<Field> = @full.field;
// Autoref supports custom borrows:
full.field.method();
// Can also use `arc` directly:
arc.field.method();
// with the method given below, these desugar to:
Field::method(@ArcRef (*full).field);
Field::method(@ArcRef (*arc).field);
impl Field {
fn method(self: ArcRef<Self>) {}
}
```
Certain custom borrows must not be allowed for `ArcRef`:
```rust
let val: Struct = ...;
let _: ArcRef<Field> = @ArcRef val.field;
//~^ ERROR: cannot borrow stack variable with `ArcRef`
let val: &Struct = ...;
let _: ArcRef<Field> = @ArcRef val.field;
//~^ ERROR: cannot borrow a reference with `ArcRef`
// and so on for raw pointers, statics and all other pointers;
// only `Arc` and `ArcRef` work as sources to obtain an `ArcRef`.
```
### `cell::RefMut<'_, T>` -- Disjoint field borrowing
The custom `@Ptr <place>` borrow should be understood by the borrow checker:
```rust
use std::cell::RefMut;
let cell: &RefCell<Struct> = ...;
let mut r: RefMut<'_, Struct> = cell.borrow_mut();
// we can borrow disjoint fields at the same time!
let field: RefMut<'_, Field> = @r.field;
let other: RefMut<'_, Other> = @r.other;
*field = Field::new();
*other = Other::new();
print_info(&*field, &*other);
// Using `r` (or `cell`) invalidates these borrows:
fun2(&mut r);
*field = Field::new();
//^ ERROR: can't use `field` anymore
```
### `NonNull<T>` -- `unsafe` Operations & Custom Pointer Compatibility
```rust
let ptr: NonNull<Struct> = ...;
// All place operations can opt-in to require unsafe:
let field: NonNull<Field> = unsafe { @ptr.field };
//^ note that `NonNull` allows implicit derefs just like `&`.
let _ = unsafe { *field };
unsafe { *ptr = Struct::new() };
```
`NonNull` offers particularly interesting borrow-compatibility with other pointers:
```rust
let my_ref: &Struct = ...;
let my_mut: &mut Struct = ...;
let arc: Arc<Struct> = ...;
let ptr: *const Struct = ...;
// Can borrow `NonNull` from `&T`, `&mut T`, `Arc<T>` safely:
let _: NonNull<Struct> = @NonNull *my_ref;
let _: NonNull<Struct> = @NonNull *my_mut;
let _: NonNull<Struct> = @NonNull *arc;
// Raw pointers still require `unsafe` to deref and then borrow:
let _: NonNull<Struct> = unsafe { @NonNull *ptr };
// Can borrow variables that live on the stack:
let _: NonNull<Arc<Struct>> = @NonNull arc;
```
### Pattern Matching
Customizable place operations extend naturally to pattern-matching, which is already based on places:
```rust
let s: Option<String> = ...;
match s {
Some("foo") => ...,
Some("bar") => ...,
_ => ...,
}
let v: &mut Vec<i32> = ...;
match v {
[] => println!("empty"),
[42, ..] => println!("answer is first"),
[.., 42] => println!("answer is last "),
[x, y, rest @ ..] => {
let _: &mut [i32] = rest;
println!("unknown state");
}
_ => ...,
}
let arc = Arc::new(MyEnum::NamedVariant { field: 42 });
// Could also imagine allowing matching directly on pointers,
// projecting every field with a "canonical" borrow
match arc {
MyEnum::NamedVariant { field } => {
let _: ArcRef<i32> = field;
}
MyEnum::Unnamed(value) => {
let _: ArcRef<u32> = value;
}
MyEnum::Unit => {}
}
```
### `Pin<P>`
Anything that comes out of the pin-ergonomics effort should also be enabled for custom pointers.
The alternative `Move` trait proposal is also compatible with virtual places. We have a strong preference for that solution, as it simplifies the proposal greatly; support for pinning in our proposal makes the `PlaceBorrow` trait more complex[^2] to account for the extra possible borrow-checker states.
[^2]: For a comprehensive description of the current idea of borrow checking, see <https://github.com/rust-lang/beyond-refs/blob/virtual-places/src/virtual-places/borrow-checker.md>. It includes the pinning states and actions, which would go away if we had `Move` instead.
### `&mut MaybeUninit<T>` & `&Cell<T>`
Wrapper types should allow field access just like structs do:
```rust
let mut s: MaybeUninit<Struct> = ...;
// Wrappers like `MaybeUninit<T>` forward all fields from `T`:
let field: MaybeUninit<Field> = s.field; // Moves `field` out of `s`.
s.field = field; // Moves it back in.
// We can also borrow a subplace:
let field: &mut MaybeUninit<Field> = &mut s.field;
```
Combining wrapper types and pointers should just work:
```rust
let r: &mut MaybeUninit<Struct> = ...;
let field: &mut MaybeUninit<Field> = &mut r.field;
*field = MaybeUninit::new(Field::new());
// Nested wrappers "Just Work":
let r: &Cell<MaybeUninit<Struct>> = ...;
let field: &Cell<MaybeUninit<Field>> = &r.field;
field.set(MaybeUninit::uninit());
// Similarly for nesting pointers:
let r: &Rc<Cell<MaybeUninit<Struct>>> = ...;
let field: Rc<Cell<MaybeUninit<Field>>> = @r.field;
field.set(MaybeUninit::uninit());
// Matching also is supported:
let Struct { field, .. } = r;
let _: Rc<Cell<MaybeUninit<Field>>> = field;
```
## Details
In this section, we go over the virtual places proposal in detail. Now is a good opportunity to take a quick pause and add questions to the discussion section regarding the claims of the previous sections.
The central trait of the proposal is `HasPlace`, it marks a type as a pointer that can be dereferenced:
```rust
pub trait HasPlace {
type Target: ?Sized;
}
```
With `X: HasPlace` and `x: X`, we can now write `*x`, but not yet do anything with that place expression. All place operations are controlled by different traits.
The operations that can be done on a place in today's Rust are:
- reading;
- writing;
- borrowing;
- moving out;
- dropping;
- dereferencing further.
We make each of these customizable using a trait, much like `Add` customizes the builtin `+` operator.
### Reading, Projections & the Shape of Place Operation Traits
The general shape of such an operation trait is this:
```rust
// not the full trait, see below
pub unsafe trait WIPPlaceRead: HasPlace {
// set to `false` to require `unsafe` context for the operation
const SAFETY: bool;
/// # Safety
///
/// Out of scope for this doc.
unsafe fn read(this: *const Self) -> Self::Target;
}
```
The custom pointer (`Self`) is made accessible in the operation function through raw pointers, because it might already be partially borrowed, moved out or otherwise only partially valid.
To support operations on only a subplace of the pointee, we introduce the concept of *projections*:
```rust
pub unsafe trait Projection: Sized + Copy {
type Source: ?Sized;
type Target: ?Sized;
fn offset(
self,
metadata: <Self::Source as Pointee>::Metadata,
) -> (usize, <Self::Target as Pointee>::Metadata);
}
```
A value of type `impl Projection` represents a subplace; an offset into a place, staying in the same allocation. A projection essentially is a chain of field access operations and array/slice indexing operations. Note that a projection can also be empty to represent the entire parent place.
The `Projection` trait will not be user-implementable and the compiler will insert the correct types and implementations when desugaring the various place operations.
All of the operation traits use a generic parameter that represents the subplace on which the operation operates. With this we can give the actual shape of the `PlaceRead` trait:
```rust
pub unsafe trait PlaceRead<P>: HasPlace
where
// `P` projects from the full place to a subplace.
P: Projection<Source = Self::Target>,
{
const SAFETY: bool;
unsafe fn read(this: *const Self, proj: P) -> P::Target;
}
```
This trait is then used to desugar reading a custom place (after borrow checking passes):
```rust
let my_ptr: MyPtr<Foo> = ...;
let val = (*my_ptr).bar;
// desugars to:
let val = unsafe {
<MyPtr<Foo> as PlaceRead<proj!(Foo.bar)>>::read(&raw const my_ptr)
};
```
The `proj!` macro doesn't actually exist; the compiler will generate a correct projection type, this requires type-information about the accessed place. We just use a macro to represent this for now.
### Writing, Moving out & Dropping
Writing is just like reading, but taking a value of type `P::Target` instead of returning it:
```rust
pub unsafe trait PlaceWrite<P>: HasPlace
where
P: Projection<Source = Self::Target>,
{
const SAFETY: bool;
unsafe fn write(this: *const Self, proj: P, value: P::Target);
}
```
Its desugaring is analogous:
```rust
let ptr: MyPtr<Foo> = ...;
(*ptr).bar = Bar::new();
// desugars to:
unsafe {
<MyPtr<Foo> as PlaceWrite<proj!(Foo.bar)>>::write(&raw const ptr, Bar::new());
}
```
Moving a subplace out requires implementing the following trait in addition to `PlaceRead`; getting the value happens via the `PlaceRead::read` function (a move is just a read + borrow-checker tracking):
```rust
pub unsafe trait PlaceMove<P>: PlaceRead<P>
where
P: Projection<Source = Self::Target>,
{
}
```
When the entire pointee is moved out of a pointer, dropping the pointer must not drop the pointee again. To ensure this, we cannot rely on `Drop` (since it takes a `&mut Self`) and instead provide the `DropHusk` trait:
```rust
pub unsafe trait DropHusk: HasPlace {
unsafe fn drop_husk(this: *const Self);
}
```
This trait functions exactly as the `Drop` impl of `std`'s `Box<T>` does today (the compiler treats `Box` specially, it first drops its contents, taking care to not drop any moved-out fields and only then calls `Box::drop`).
When a pointer's place is only partially moved out, we require the pointer to implement the `PlaceDrop` trait to drop all of the remaining subplaces (after that we call `drop_husk`):
```rust
pub unsafe trait PlaceDrop<P>: HasPlace
where
P: Projection<Source = Self::Target>,
{
unsafe fn drop(this: *const Self, proj: P);
}
```
To avoid confusion, `Drop` and `DropHusk` must not both be implemented for the same type; if they are, a compiler error will be emitted. When one drops a `Ptr: DropHusk` without moving out fields, the compiler requires that `Ptr: PlaceDrop<empty_proj!()>` holds. It then first drops the contents and then calls `drop_husk` just like it does for `Box` today.
### Borrowing
A pointer can opt-in to its contents being borrowed as another pointer. That's an n-to-m relationship, e.g. `Arc` can be borrowed as `&`, `ArcRef`, `NonNull`, or types defined in downstream crates. `PlaceBorrow` describes this operation.
```rust
/// `X` is the pointer we borrow with.
pub unsafe trait PlaceBorrow<P, X>: HasPlace
where
P: Projection<Source = Self::Target>,
X: HasPlace<Target = P::Target>,
{
const SAFETY: bool;
/// The kind of borrow to the subplace `P` taken by the new pointer.
const BORROW_KIND: BorrowKind;
/// The duration of the borrow.
type BorrowDuration: BorrowDuration;
unsafe fn borrow(this: *const Self, proj: P) -> X;
}
impl<'a, P: Projection> PlaceBorrow<P, &'a P::Target> for &'a P::Source {...}
impl<'a, P: Projection> PlaceBorrow<P, &'a P::Target> for &'a mut P::Source {...}
impl<'a, P: Projection> PlaceBorrow<P, &'a P::Target> for Arc<P::Source> {...}
impl< P: Projection> PlaceBorrow<P, ArcRef<P::Target>> for Arc<P::Source> {...}
impl< P: Projection> PlaceBorrow<P, ArcRef<P::Target>> for ArcRef<P::Source> {...}
```
`PlaceBorrow` integrates into the borrow-checker by telling it what the nature of the borrow is; these details are not fully finalized and out of scope for now. Below is a sketch of what that could look like.
```rust
/// The kind of loan to the subplace `P` taken by the new pointer.
pub enum BorrowKind {
/// Like `&T`: allows simultaneous other shared operations,
/// but none requiring exclusivity.
Shared,
/// Like `&mut T`: no simultaneous operations allowed on `P`.
Exclusive,
/// Like `*const T`: not tracked by the borrow checker at all.
Untracked,
}
/// The duration of the loan tracked by the borrow checker.
///
/// The borrow checker ensures that `this` has `ACCESS` rights to `P`.
#[sealed]
pub trait BorrowDuration {}
// Types implementing `BorrowDuration`:
/// For only the duration of the `PlaceBorrow::borrow` call.
pub struct Instant;
/// For the duration of `'a`.
pub struct Lifetime<'a>;
/// Until the pointer is dropped.
pub struct Indefinite;
```
We are working on an implementation in a-mir-formality to obtain a more formal model that will guide our final proposal in this area.
The desugaring of borrowing is a bit more involved:
```rust
let ptr: MyPtr<Foo> = ...;
let res = @MyOtherPointer (*ptr).bar;
// desugars to:
let res = unsafe {
<MyPtr<Foo> as PlaceBorrow<proj!(Foo.bar), MyOtherPointer<...>>>::borrow(&raw const ptr)
};
```
The `<...>` part in `MyOtherPointer` is short for "instantiate all generics with inference parameters".
### Non-indirected containers
To support forwarding fields to wrapper types:
```rust
// `MaybeUninit<Struct>` has a virtual field `field`
let r: &mut MaybeUninit<Struct> = ...;
let field: &mut MaybeUninit<Field> = &mut r.field;
// Nesting wrappers just works:
let r: &Cell<MaybeUninit<Struct>> = ...;
let field: &Cell<MaybeUninit<Field>> = &r.field;
```
We have the `PlaceWrapper` trait. It turns a projection on the wrapped type to a projection on the wrapper; details pending.
```rust
pub unsafe trait PlaceWrapper<P>: HasPlace
where
P: Projection<Source = Self::Target>,
{
/* ... */
}
```
### Nested Pointers
Perhaps non-obviously, we need something special for operations on pointers-within-pointers, e.g.:
```rust
let mut x: Box<Arc<Struct>> = ...;
let field = @ArcRef x.field; // needs access to the inner `Arc`
// desugars to
let field = @ArcRef (**x).field;
```
This nested dereference requires a new place operation to obtain a raw pointer to the `Arc` to perform the actual `PlaceBorrow` operation.
```rust
pub unsafe trait PlaceDeref<P>: HasPlace
where
P: Projection<Source = Self::Target>,
P::Target: HasPlace,
{
unsafe fn deref(ptr: *mut Self, proj: P) -> *const P::Target;
}
```
Pointers returned by `PlaceDeref::deref` are exclusively used for further place operations, never used in another way (for example: they are never dereferenced).
Desugaring this operation is a highly technical exercise; we have a [sketch of a partial algorithm](https://github.com/rust-lang/beyond-refs/blob/virtual-places/src/virtual-places/dereferencing-places.md#desugaring-dereferences) that is out-of-scope for this meeting.
### Summary
In this section, we defined the following traits to customize the existing place operations of Rust for custom pointers:
- `HasPlace` defines types that work like pointers;
- Each operation that can be done on a place is made available via a trait:
- `PlaceRead`
- `PlaceWrite`
- `PlaceBorrow`
- `PlaceMove`
- `PlaceDrop` & `DropHusk`
- `PlaceDeref`
- `PlaceWrapper` defines types that wrap values.
This approach encodes the existing features of builtin references and allows user-defined pointers to take advantage of the same features. It also is highly flexible and allows extreme control over how custom pointers are borrow-checked.
In addition to these low-level controls, we want to design higher level ones that require less (or no) `unsafe` and are easier to grasp. For example, the `Deref[Mut]` traits could offer a simple an safe way to implement a collection of place operations all at once.
### Extensions for a *Shiny Future*
This section is to give a sense of what extensions we can do in the future to make the proposal even more powerful. The place approach neatly incorporates with other language features.
#### Enum Projections
We already mentioned that we want to make pattern matching work, for that we need `Projection` to support enums. They already do support offsetting to a field of a variant, but they lack the ability to ensure that the variant is live. For this we will need to add a `PlaceReadDiscriminant` trait that provides a way to read the discriminant of a pointer when an enum is contained.
#### Unsized Types
Our `Projection` trait already relaxes `Sized` bounds and handles metadata. To also support unsized values behind pointers, we need to extend our proposal:
- a `PlaceMetadata` trait allows the metadata to be read from pointers, this is required to be able to call methods on `MyPtr<MyUnsizedType>`.
#### Custom Projections
Currently, `Projection::offset` returns a `usize`; there are several types that would like to customize this. Index projections are essentially the way we support `Index[Mut]`; but those traits allow for any type to be the index. If we also lift that restriction for indexing projections, we could make the following syntax work for `HashMap`:
```rust
let mut map: HashMap<&'static str, Value> = HashMap::new();
// Calls `map.insert("my_key", Value::new());
map["my_key"] = Value::new();
```
## Next Steps: The Plan for 2026
We [recently posted](https://github.com/rust-lang/rust-project-goals/issues/390#issuecomment-4099385451) an update on the tracking issue of the project goal summarizing our plan for 2026:
> We have an updated plan for this goal in 2026 consisting of three major steps:
> - `a-mir-formality`,
> - Implementation,
> - Experimentation.
>
> Some of their subtasks depend on other subtasks for other steps. You can find the details in the updated tracking issue <https://github.com/rust-lang/rust/issues/145383>. Here is a short rundown of each:
>
> **`a-mir-formality`:** we want to create a formal model of the borrow checker changes we're proposing to ensure correctness. We also want to create a document explaining our model in a more human-friendly language. To really get started with this, we're blocked on the new expression based syntax in development by Niko.
>
> **Implementation:** at the same time, we can start implementing more parts in the compiler. We will continue to improve FRTs, while keeping in mind that we might remove them if they end up being unnecessary. They still pose for a useful feature, but they might be orthogonal to field projections. We plan to make small and incremental changes, starting with library additions. We also want to begin exploring potential desugarings, for which we will add some manual and low-level macros. When we have that figured out, we can fast-track syntax changes. When we have a sufficiently mature formal model of the borrow checker integration, we will port it to the compiler. After further evaluation, we can think about removing the `incomplete_feature` flag.
>
> **Experimentation:** after each compiler or standard library change, we look to several projects to stress-test our ideas in real code. I will take care of experimentation in the Linux kernel, while @tmandry will be taking a look at testing field projections with `crubit`. @joshtriplett also has expressed eagerness of introducing them in the standard library; I will coordinate with him and the rest of t-libs-api to experiment there.
## Related Work
Field projection is related to a lot of other ongoing efforts:
- `Reborrow`
- it solves a different, but related issue
- Both proposals are compatible with each other, if we add both, there will be two ways to reborrow:
- via the `Reborrow` trait, essentially a borrow-checker controlled `Copy`: no custom code is run to reborrow a variable, it is memcopied verbatim.
- via the `PlaceBorrow` trait: `@*ptr` or `@MyPtr *ptr`, in this case custom code is run.
- pin-ergonomics and `Move` trait
- both of these efforts try to improve `Pin`, either by making it builtin to the borrow checker or by baking it into the type system
- virtual places can work any of the three worlds (accepting `Move`, making `Pin` builtin, or keeping the status quo)
- there is a preference for `Move`, since it greatly simplifies the design and the design space
- in-place initialization
- some ideas in in-place init could benefit a lot from field projections via virtual places
- virtual places do not depend on in-place init
- some new traits for supporting directly writing to a pointer via an initializer make the two proposals compatible
- Reflection
- it is not yet clear how big of a role reflection can play in virtual places
- the `Projection` trait is a good target for more experimentation
- custom DSTs
- can be supported the same way we would support `?Sized` types: via `ptr_metadata` and a new place operation trait to obtain the metadata from a pointer.
- `DerefMove` and `DerefRaw`
- become obsolete, as all use-cases should be handled by virtual places
- `&own T`
- can now be implemented as a library type pending more work on the exact shape of `PlaceBorrow`
## Thanks
I want to take a quick moment to thank the people that helped produce this document:
- Nadri for being deeply involved in the drafting process. We had several meetings in which we planned and structured our approach; I then prepared a draft, which we improved, corrected, and polished together.
- Tyler for giving general guidance on the document, giving lang-team-consumption feedback, and reviewing the intial and final drafts.
- Xiang, Alice, and Gary for their suggestions and feedback on our initial draft.
I also want to thank Nadri again for coming up with the place based idea, it made the last half of my year! Also many thanks to Tyler for being my lang champion since last year, he has helped on so many levels and I truly feel like we're getting somewhere. Xiang too has been incredibly supportive as my mentor for the compiler changes, so many thanks to him again! We were finally able to land field representing types that we worked on since September.
---
# Discussion
## Attendance
- People: Josh, TC, Nadri, Tyler Mandry, Niko, Benno Lossin, Mark (simulacrum), Jack Huey, James Muriuki, Tomas Sedovic, Gary Guo, Aapo Alasuutari
## Meeting roles
- Driver: TC
- Minutes: Tomas
TC: Benno, think about: what do you want out of this meeting?
## Vibe checks
### nikomatsakis
I didn't have time to read the doc. Generally in favor from my prior knowledge.
### tmandry
Very excited about this work, and I think Benno and Nadri did an excellent job putting this doc together.
What I love about this direction is how effectively it builds on what Rust already has. I love to see designs that reinforce our existing concepts while pushing them in directions that make them more expressive.
### scottmcm
(Not present.)
### Josh
I adore this! I love how orthogonal it is, and how impactful and universal it is. I anticipate this becoming a beloved, *pervasive* feature of Rust.
Nadri: <3
Benno: <3
Places and projection seem important enough to me that they're worth giving one of our precious remaining ASCII sigils to, and `@` is nicely evocative of a place (something is *at* a place). So to the extent the final syntax benefits from a sigil, :+1: for giving this `@`. (See some feedback below on the details, though.)
Much appreciation for the explicit "What we'd like to get out of this meeting" and *especially* "Explicitly out of scope, to stay focused", precisely because we tend to want to delve into (or get nerd-sniped by) the details. :) Also, "Now is a good opportunity to take a quick pause and add questions to the discussion section regarding the claims of the previous sections.", you know us so well. :)
Some of my responses below are intended in the spirit of "give design constraints to the people working on it, and let them do the design, rather than saying what exactly the design should be". I look forward to reading the designs in more detail when we get to that point, and I'd be happy to review and give vibes on incremental designs if that helps.
> The `Projection` trait will not be user-implementable
Big :+1: here, and let's not change that lightly. And *because* it isn't user-implementable, I don't have much in the way of concerns of it being built substantially on `unsafe`. I used to worry about that and wish it could be done more safely, but thinking about the general class of smart pointers, I don't think it's worth complicating the design to make their *implementation* safer. They're not *complicated* as-is, just unsafe.
`DropHusk` looks *fascinating* and I'm wondering if this would solve several *more* problems than just this.
:+1: for borrowing non-canonical types.
"canonical borrowing" seems like a good idea. *Not* bikeshedding the exact syntax here, but giving vibes-level feedback for the team working on this to consider: `@full.field` has the "go back and add something at the beginning" problem. It would be nice to start out by typing `full` (or an expression producing it) and then keep typing at *that* point and end up with an ArcRef to `field`. (That applies to borrows as well, of course; this isn't specific to field projection.) Also, given that we allow autoref to just write `full.field.method()`, I would be generally favorable towards *not* needing as much explicit syntax for this at all; I feel like this doesn't *have* to be signposted.
Benno: We've thought about having postfix syntax like `full.@field` or various other things. The syntax is still an open question, so happy to do lots of experimentation once we have a compiler experiment. (feel free to move to a topic)
I love how this works with pattern matching! Obvious note (doesn't need discussing here) that this needs syntax for explicitly specifying the type you're borrowing in the pattern, and that syntax will need to not have corner cases that conflict with the current use of `@` in patterns. I don't anticipate that being a problem.
(note while reading re places and future move-only fields)
Nadri: That's part of the plan
### TC
Love it. High concept. As I said in the last meeting:
> "I particularly like language features that reduce the need for library surface area, and this is one of those."
There are, of course, many details to resolve and understand further, e.g., with respect to migration issues, interaction with `const`, `async`, and other effect-like things, etc. I'm looking forward to seeing the formalization work.
### Jack
Whoo boy. This is great. There's so much here that I'm not exactly sure where to begin and what to comment on. I think this is the type of thing that we will only *really* be able to figure out the nitty gritty details and ergonomics only after some amount of experimentation.
Agreeing with others that this feels like just distilling out what *already* exists for different parts of Rust, often non-generalizable or unable to be currently captured by users, and making them a "first-class" thing - that's very much in the spirit of the language. I *also* like that you've thought about the "high-level" experience too: Things like the role of `Deref[Mut]` with this set of features. This is *also* part of the language (have your cake and eat it too). I'd continue to encourage thinking (and experimenting) more with how to make that *even better*: the `@` sigil seems like a nice low-level thing, but it would be have this be the type of thing that shouldn't be used by ~95% of users, others it exposes these "lower-level" details too much, I think.
Josh: +1 for the concept that users will encounter this but shouldn't need to deal with the guts of it. It seems we're mostly keeping it that way based in the doc.
Tyler: That might be worth digging into. Jack it sounds you'er saying you don't want most users to see code with `@`.
Jack: The `@` sigil I don't know. But I don't think it should make for 95% of users to make the code they write to be more complicated. With the `@` sigil they have to think about using `@` vs. `&` etc.
Nadri: Jack, is that a fair summary: we should be careful not to add cognitive load to users when designing this feature?
Jack: Yes. We should be careful to not add cognitive load to 95% users of the feature.
## Ask the author
TC: Benno, thank you for this document, for joining us to talk about it, and for all of your work on this. What's most helpful to you to get out of this discussion?
Benno: Thanks for the warm welcome. The first thing we wanted to get out was socializing this idea and getting y'all excited about this feature. It would also be cool to get you to experiment with it when we have a compiler experiment (partially) ready. And think about where you want to apply this. Get usecase you see for this for us. We've given a holistic view for existing users of pointers, but did we forget any features? Are there things pointers would sometimes want to do that we don't cover? And for the detail section, do we have any concerns about moving forward to the compiler experiemnt?
TC: So those were:
- Use cases we see for this.
- Forgotten features.
- What the ergonomics of raw pointers should look like.
- Concerns about moving forward.
Josh: +1 go do an experiment. I assume the scope of the experiment is to do enough to sketch this and get to a design you want to RFC. Is the next milestone RFC and design meeting? Or is it "here's oru sketch for the design details, does that look directionally correct?"
Benno: Depends on the experiment itself. We'd like to implement a version that doesn't include borrow checking at all. At the same time we want to have a formal model in a-mir-formality for the borrow checker. And then we want to start implementing that in the compiler. Depending on when we encounter hard problems, we may need to require a new design meeting. We expect the borrow-checker is going to be the first major hurdle. If everything goes well the next call would be the RFC.
Josh: That makes a lot of sense. When you get to the vibe of "we got to the 90% of the design and we think it's right, 'just' need to get the borrow checker happy", that might be a good point for a document for people to get a feel for. Might be a good point for a conversation.
TC: Tyler, as a champion, does this list look like a reasonable list of things to talk through?
Tyler: I want to discuss the cognitive load.
Benno: For the usecases point, I think that's best done asynchronously.
## From author: Use cases we see for this
TC: Let's think about this one async unless people have some immediate thoughts to note down.
Tyler: Josh mentioned Option and Result. (`Option<Struct> -> Option<Field>` in some fashion, behind a pointer)
Gary: Some immediate thoughts: I think it can probably be projected, but neither PlaceRead/PlaceWrite would work for `Option` because it expects `::Target` and not `Option<::Target>`
## From author: Forgotten features?
TC: Anything stand out to people that they'd like to see here that wasn't covered?
Mark: The only two things I have are in the doc.
## From author: Concerns about moving forward
TC: Anyone have concerns about moving forward?
Mark: One "concern" (meta-question/whatever), if/as we move into standard library experimentation, please do loop T-libs (happy to help!) in on making sure we have good docs for standard library reviewers. `const` trait, `SizedOfVal`, etc. have I think partially not succeeded at making it easy for me as a library reviewer to know what to approve, who to cc, etc.
Benno: Happy to. Let me know who to ping.
Mark: I'm reviewing most of the libs PRs, happy to get the docs reviewed.
TC: Thank you for that!
Josh: We need to extend that to avoid burnout/lottery factor here.
## From author: Ergonomics of raw pointers
TC: Elaborate on that for me, Benno, what sort of questions do you want to discuss here?
Benno: Are there design axioms around what the syntax should feel like? Jack's point of "don't make 95% of users view different code from what they do today" is a good point.
Benno: Instead of using the `@` symbol, we could repurpose `&`.
TC: Does that work? I had that question when reading the document. It'd be appealing if it could work.
Benno: We'll figure that out.
Benno: I think the idea was mostly it would be confusing to get a raw pointer out of an expression that has an ampersand. We want to have a place borrow implemented for raw pointers.
TC: We already use ampersand for raw pointers: `&raw`.
Benno: But this would be just with an `&`, which would be actually not possible, since what would a normal reference be spelled with?
Josh: The ambiguity about making a new temporary and making a new address of it vs. taking a field/place borrow. When you're writing `@ArcRef...` it's clear you're borrowing an `ArcRef` from something. If you wrote `&ArcRef` it's ambiguous to people reading this.
Nadri: I'm thinking of Stroustrup's Rule. People would be confused today, but maybe in 5 years it would be natural.
Josh: Agreed right now to do that. But maybe in the future it will get less people confused and we could change it then.
TC: That's interesting. I think of Stroustrup's Rule more as a cautionary tale than as a prescription.
Jack: It's a fundamental question of what `&` means to us. Today it means "you take a normal reference". It's already more than that with `&raw`. There's still additional syntax there but maybe we're getting at the idea that references in general aren't always just normal references but also custom references. My intuition isn't that weird to have `&` operator to mean "take a reference" and that's not the same as `&T`. But that's not the first priority. The first priority is to start doing the experimentation. And we'll do a user research team, we'll figure out if people are confused if the change the meaning of `&`.
Tyler (in chat): Did we just find another use for `share` as a keyword.. 👀
Jack: I don't like starting with the `@` now and eventually transitioning to `&`. But maybe we could do a trait call. Maybe there are some methods that give you wthat you want. Some middle ground that let you work in the immediate term until we settle this.
Benno: I'm going to stop the syntax discussion here. But it is important for the ergonomic parts. We should talk about the ergonomic axioms we'd want if we can figure that out.
TC: To the question about design axioms, what I'd suggest is that if this could end up superseding references as they exist today, that would be interesting. And that opens a lot of syntactic options. Can we grow the language by generalizing what we do today? That'd be an interesting direction for exploration.
Nadri: It kind of does. We've made the design thinking about taht; we want to do everything builtin references can do and we're mostly there I think.
Benno: Exactly. We also thought of this from the perspective of these traits working for the built-in references and we want to implement them for those too.
TC: Makes sense to me.
Tyler: There was something about "don't increase the cognitive load to 95% of the users". I like that as an axiom.
Niko: What are the truisms? Things in your mind you're holding onto? When you're stuck you look to them. What are the signposts that help you pick? Those are your axioms. Sounds like you're generalizing existing types, fitting into patterns, leaving no usecase behind.
Nadri: Having custom pointers feel the same as built-in ones.
Niko: What are the litmus tests that you run down this to see how it would work?
Nadri: One of them is that we want `Box` to not be magic. And other pointers in the stdlib like `Arc`: how far can we stretch them, what might users naturally want to do with them?
TC: Benno and Nadri, I think we're going to turn this one around. Many of us, I think, would like to hear from you about your axioms -- not right now, as we're short on time -- but perhaps you could leave them in the meeting thread afterward.
Benno: Sounds good.
## From champion: Cognitive load
Tyler: I thikn we've covered it. Jack mentioned if user has to start thinking aobut whether to use `@` or `&` I can see that increasing cognitive load. I tihkn that's related to the syntax question. +1 to what you said TC, it's interesting if we can subsume references.
Nadri: On the cognitive load: we're making more implicit code happen. Not that much more than `Deref` but being able to track where code is run will be harder.
## (answered) Question about "against doing field-by-field projections"
Gary:
```rust
let foo: &mut Foo = ...;
let a: &mut A = &mut foo.bar.a;
*a = ...; // this write activates the pointer `a`
let b: &mut B = &mut foo.bar.b; // the intermediate borrow to `foo.bar` invalidates `a`
// ^ why does this invalidates `a`? Is this because foo.bar makes use of `DerefMut::deref_mut`?
*a = ...; // UB
*b = ...;
```
Nadri: to be clear, this code today is fine. the argument is "if `&mut foo.bar.a` meant `&mut (*(&mut foo.bar).a)`", then there would be an issue
Nadri: so maybe more accurately, we're saying: if you define `struct MyRefMut<'a, T>(&'a mut T)` and want to give it field projection powers, then the version that does field-by-field causes UB/borrowck issues that don't exist for `&mut` itself.
Since we're aiming for ergonomic parity between builtin and custom pointers, that's an issue.
## (answered) Question about "must be done all at once"
Josh:
> **Conclusion:** projections must be done all at once; no intermediate pointer values should be created when composing projections.
Verifying something: it will still be *possible* to project step-by-step, if that's what the user desires, it just can't be the *only* way due to the multiple-mut-borrows problem, right?
Nadri: I mean you can always write `let foo = @Ptr x.foo; let bar = @Ptr foo.bar` if you want to do it field by field
Josh: :+1:
## (answered) Possible parsing difficulty with "canonical borrow"
Gary: It looks like the `@ty place` and `@place` are difficult to distinguish in parsing.
Benno: We're aware of that issue, we'll leave syntax bikeshedding to a later point in time.
Gary: :thumbsup:
Benno: also note that the `@ty place` syntax currently only allows `ty` to be an ident. to specify generics/a path, we intend to offer the `@<ty> place` syntax, here `ty` can be a path with arguments as usual.
Gary: Probably you can also just use `&<ty> place` syntax, given `&` is one special type of `@` (canonical one is just `&<_> place`).
Josh: Possible parsing ambiguity: You can start an expression with `<...>::`, so you could have `@ArcRef <...>::`. `ArcRef<...>` looks like a type. Normally, you have to turbofish a type like that in expression context, but is there any possble ambiguity there?
## Support Read/Write of non-Place::Target?
Mark: I'm wondering if there are use cases for supporting read/writes of multiple types (maybe bitfields -- not sure?). Maybe this is supported indirectly via borrowing to some other type and then write/read of that?
Benno: I'm not sure I follow, you mean allowing `*my_ptr = a;` and `*my_ptr = b;` for `a` and `b` of different types? So for example `a: i32` and `b: i16`.
Mark: Yes. I'm not sure I have a great example off the top of my head for *when* you want this. I think I recall some bitset/bitfield crates struggling with wanting to support multiple types on read/write... maybe it was `bool` and some custom `i1`-like type?
Benno: I've run into the limitation of only being allowed to write/read one type (or rather wanting to read/write a type that's different from `<Self as HasPlace>::Target`) a few times. It essentially is part of the core idea of places, a place expression must have a single type. Nadri may be able to comment more about that. I think it sounds like a useful thing to experiment with, but inference or other more fundamental issues might pop up.
Mark: I think looking at what https://docs.rs/bitvec/latest/bitvec/#bit-field-memory-access sets up may be useful, that's what prompted this question. But yeah, more to see if there's something there as we do the design work than a concrete use case from me at this point.
Gary: I think it *might* work if we have the ability to have a custom fallbacks for type inference variables.
---
Benno: I've been hitting this limitation myself. Can we lift this restriction? And Nadri said it doesn't fit our proposal and I follow his intuition. But I still want this feature.
Nadri: Is it being able to write different types to the same place?
Benno: Yes.
Nadri: I feel like a place has a type and you write a value of that type to that place. That's the model with today's place. Stretching that model feels meh on a gut feel, I don't see it yet. I'm open to be proven wrong though.
TC: Benno, are you proposing writing to a place with a different type than the type of the place? (Benno: Yes.) In programming language theory, this is called "strong updates". It's an interesting advanced type system feature. Look up as well "session types".
Nadri: I think that's interesting to have but different from what Mark was asking.
Gary: If we support custom types that aren't target, what Josh proposes for the Option and Result types might work.
Tyler: With `Option` you could support just writing the inner value into the option. I think Swift does this.
Niko: Seems like what we're discussing is equivalent of "impl into".
Benno: Sort of. You can run custom code, you can have a smart pointer that depending on a type could write to a different register.
Niko: Yes. You could say "I can accept different types and behave differently based on those".
## Re-assembling?
Mark: The current proposal seems to work well for disassembling / projecting out from a type. I'm wondering if you've thought about what feels like related transformations (e.g., `Cell<[T]> -> [Cell<T>])...` is that in scope? How should we think about that? (Not sure what the "name" for that is.)
Nadri: interesting, with our `PlaceWrapper` we should support something like:
```rust
fn foo(x: &Cell<[T]>) {
let element = &x[i];
// element: &Cell<T>
}
```
So `Cell<[T]>` behaves quite close to `[Cell<T>]`. But we don't support making that whole transformation
Benno: I have thought about supporting that at some point, I think. Should be generally possible to integrate that with our `PlaceWrapper` trait, I think. Only questions are, how do you spell it.
Nadri: hmmmmm :D `Cell<(A, B)>` to `(Cell<A>, Cell<B>)` too I imagine? you'll have to show me how you see that working
Mark: Semi-related thought, this feels plausibly related to the in-place-init work insofar as that also seems to want `MaybeUninit<(A, B)>` -> `(MaybeUninit<A>, MaybeUninit<B>)` -> `(A, B)` and possibly vice-versa. If we can find some way to support something here it would probably help avoid needing a bunch of extra methods all over std surface area too.
Benno: indeed, for the destructuring part, we've wanted FP from the very beginning. The re-assembly is the difficult part, since it also needs information to flow from the borrow checker about all subplaces being initialized. I'm not sure if we can unify that with the destructuring part, since in-place init design is very hard.
## Trait for reading discriminant for pattern matching
Gary: For pattern matching, it looks like it needs the ability to read disciminant.
Benno: see the "Extensions for a Shiny Future" section later on.
Gary: This is more complicated for types with niches, right? Because you don't have the discriminant directly stored in a single place, but you need to possible read the whole value to determine the discriminant.
Nadri: that's fine, it can read the whole value range if it needs to. what would be a problem is if we mistakenly considered the discriminant to be like a field disjoint from the rest of the data, but we're not making that mistake :)
Benno: I think it still is relevant to borrowck, so for example if you have the contents of a `Option<&T>` borrowed mutably, you're not allowed to read the discriminant. But it seems easily solvable
Gary: I believe Rust used to make that mistake, and the niche-free nature of `UnsafeCell` was added later on to fix soundness bug. We definitely need to be careful in supporting this.
Nadri: yea +1 Benno that's somewhere the borrow-checker may be a bit wrong today. @Gary: noted; will need care for sure, especially when we flesh out the exact unsafe contracts for out traits
***
Gary: We don't have a current answer of how to make it work it's mentioned in the future extension section in the doc
Nadri: I think we do, can talk offline
## (answered) Interaction with type inference
tmandry: I've been wondering about the use of generic params on the traits and whether they would create problems for type inference. As I think through the cases it doesn't seem like it'll cause immediate problems, but do you think there might be issues we run into when composing a few of these features together?
Benno: With the current version in the doc, there will be no issues. With certain extensions, we might run into some inference problems.
Nadri: for `PlaceBorrow`? the way the feature is designed, it's only invoqued when the target type is already known: either via autoref or an explicit `@Type place`.
tmandry: The degrees of freedom I see are
* Base type
* Target type
* HasPlace type
Nadri: these are associated types, they aren't typically inferred right?
tmandry: Yeah, I can't think of a case where it's ambiguous as long as you know the base type you're projecting from.
Gary: I think it's as ambiguous as a field access would be (you'll need to know the base type already anyway?)
tmandry: Actually, can there be ambiguity here? If there's no type annotation on the second line, could there be more than one way to interpret `&r.field`?
```rust
let r: &Cell<MaybeUninit<Struct>> = ...;
let field: &Cell<MaybeUninit<Field>> = &r.field;
```
Benno: no, how?
tmandry: ok, in this case we're just following the normal HasPlace::Target assoc type, not "changing pointer kinds" (in which case it would use `@` and be explicit)
tmandry: The last area I'd be worried about then is interaction with coercions.
Benno: I don't think we've thought much about that yet, but I think that's also best explored with the compiler experiment. We might not always want to apply coercions if possible. It at least feels a bit weird to do them in the first place, since you shouldn't be able to access "more fields" by coercing a value...
## Name conflicts
Gary: Would this create name conflicts between those of the place and those of the wrapper type?
For example:
```rust=
struct Foo<T> {
foo: *const T
}
// Foo<T>: HasPlace<T>
struct Bar {
foo: u32,
}
```
Nadri: yeah there's a need for disambiguation here, we settled on priorizing the outermost visible one in case of conflict (following existing `Deref` behavior)
Gary: I think raw pointers are not receivers for similar reasons. Perhaps T-libs-api want to say something about this?
Benno: Can't you already encounter this problem today if you implemented `Deref` for `Foo`?
Gary: You can, which is why `Deref` types in std are very carefully not exposing any fields or methods. But given you're implementing virtual places for existing std types with already methods (like Cell, NonNull and pointers), it would be a source of conflict?
Benno: The source of conflict would be fields, no? Those are all private for most of std's types and especially for those that would want to implement `HasPlace`, or am I forgetting something?
Nadri: the one thing we add that changes is that raw pointers would get autoderef under our proposal, so in that sense maybe. I don't think we add other ambiguities?
---
Gary: Users can define methods on these custom types and these could conflict with future methods added to the standard library. This probably needs some Libs-API input.
## Projecting through `Option` and `Result`
Josh: I'd like to be able to project through `Option` and `Result`, for instance going from `Option<Struct>` to something *like* `Option<&Field>` (modulo that you can't manufacture an in-memory `Option<Field>`, so this might interact with move-only fields).
Nadri: Benno and I disagree on this one :D
Josh: I'd love to hear more, I'm really interested in this, and so is libs-api with an eye towards eliminating a lot of simple helpers/combinators.
## `const`-ness of offsets, and alignment implications
scottmcm: For your list (probably not for meeting discussion), but one thing I've often pondered is a new and better raw pointer type that can also carry alignment validity, so you can talk about both `RawPtr<[u8; 4], Align = 4>` and `RawPtr<[u8; 4], Align = 1>`. It would thus be nice for projecting/offsetting inside such a thing to be able to update the *type* accordingly -- and that would need `const` offsets. After all, in a `(u16, u8)` that `u8` is actually 2-aligned, even though the type isn't. (Insert appropriate caveats here that the previous isn't actually about tuples since those don't have enough layout semver promises, but same idea for `repr(linear)` or whatever.)
Nadri: offsets can't in general be a `const` because we support indexing projections like `x[i]`, but I can imagine a `ConstProjection` traits for projections that are? seems like something that can be added without too much trouble
TC: This reminds me of what I raised in my vibe check: we'll need to think about all of our effect-like things.
Gary: Can we just use `const Projection` w/ const trait impl feature?
## "We should not increase the cognitive load for 95% of users"
Jack: When I see the `@` sigil, my immediate thought is: this seems like it useful to add to be able to use the full power of this feature, but is also not something that I want to see 95% of users need to use or think about. Adding these should not make most people need to think harder about writing Rust. (I actually see `&raw` as one of these types of things.)
nikomatsakis: Jack and I have been exploring this concept of a user research team. It occurs to me that this might be a thing we can explore experimentally -- how much cognitive load is it? Are there hypotheses we can test?
## What doors are we closing
tmandry: I'm thinking about ways we might make pointers more convenient, with a more elaborate set of coercions and autoref in param position. I'd like to explore how this feature composes with those.
Tyler: I have a lot of interest in other ways of reducing cognitive load. E.g. reborrow traits
## Adjournment
TC: Big round of applause for our authors and experimenters. We'll continue the discussion in the meeting thread. I look forward to all the experimentation that comes out of this and the formality/formalization work. Thanks to everyone for joining us.