|
| 1 | +# Field Projections |
| 2 | + |
| 3 | +| Metadata | | |
| 4 | +| -------- | ------------------------------------------------------------ | |
| 5 | +| Owner(s) | @y86-dev | |
| 6 | +| Teams | [lang], [libs], [compiler] | |
| 7 | +| Status | Proposed | |
| 8 | + |
| 9 | +## Summary |
| 10 | + |
| 11 | +Finalize the [Field Projections RFC] and implement it for use in nightly. |
| 12 | + |
| 13 | +[Field Projections RFC]: https://github.com/rust-lang/rfcs/pull/3735 |
| 14 | + |
| 15 | +## Motivation |
| 16 | + |
| 17 | +Rust programs often make use of custom pointer/reference types (for example `Arc<T>`) instead of |
| 18 | +using plain references. In addition, container types are used to add or remove invariants on objects |
| 19 | +(for example `MaybeUninit<T>`). These types have significantly worse ergonomics when trying to |
| 20 | +operate on fields of the contained types compared to references. |
| 21 | + |
| 22 | +### The status quo |
| 23 | + |
| 24 | +Field projections are a unifying solution to several problems: |
| 25 | +- [pin projections], |
| 26 | +- ergonomic pointer-to-field access operations for pointer-types (`*const T`, `&mut MaybeUninit<T>`, |
| 27 | + `NonNull<T>`, `&UnsafeCell<T>`, etc.), |
| 28 | +- projecting custom references and container types. |
| 29 | + |
| 30 | +[Pin projections] have been a constant pain point and this feature solves them elegantly while at |
| 31 | +the same time solving a much broader problem space. For example, field projections enable the |
| 32 | +ergonomic use of `NonNull<T>` over `*mut T` for accessing fields. |
| 33 | + |
| 34 | +In the following sections, we will cover the basic usage first. And then we will go over the most |
| 35 | +complex version that is required for [pin projections] as well as allowing custom projections such |
| 36 | +as the abstraction for RCU from the Rust for Linux project. |
| 37 | + |
| 38 | +[pin projections]: https://doc.rust-lang.org/std/pin/index.html#projections-and-structural-pinning |
| 39 | +[Pin projections]: https://doc.rust-lang.org/std/pin/index.html#projections-and-structural-pinning |
| 40 | + |
| 41 | +#### Ergonomic Pointer-to-Field Operations |
| 42 | + |
| 43 | +We will use the struct from the RFC's summary as a simple example: |
| 44 | + |
| 45 | +```rust |
| 46 | +struct Foo { |
| 47 | + bar: i32, |
| 48 | +} |
| 49 | +``` |
| 50 | + |
| 51 | +References and raw pointers already possess pointer-to-field operations. Given a variable `foo: &T` |
| 52 | +one can write `&foo.bar` to obtain a `&i32` pointing to the field `bar` of `Foo`. The same can be |
| 53 | +done for `foo: *const T`: `&raw (*foo).bar` (although this operation is `unsafe`) and their mutable |
| 54 | +versions. |
| 55 | + |
| 56 | +However, the other pointer-like types such as `NonNull<T>`, `&mut MaybeUninit<T>` and |
| 57 | +`&UnsafeCell<T>` don't natively support this operation. Of course one can write: |
| 58 | + |
| 59 | +```rust |
| 60 | +unsafe fn project(foo: NonNull<Foo>) -> NonNull<i32> { |
| 61 | + let foo = foo.as_ptr(); |
| 62 | + unsafe { NonNull::new_unchecked(&raw mut (*foo).bar) } |
| 63 | +} |
| 64 | +``` |
| 65 | + |
| 66 | +But this is very annoying to use in practice, since the code depends on the name of the field and |
| 67 | +can thus not be written using a single generic function. For this reason, many people use raw |
| 68 | +pointers even though `NonNull<T>` would be more fitting. The same can be said about `&mut |
| 69 | +MaybeUninit<T>`. |
| 70 | + |
| 71 | +Field projection adds a new operator that allows types to provide operations generic over the |
| 72 | +fields of structs. For example, one can use the field projections on `MaybeUninit<T>` to safely |
| 73 | +initialize `Foo`: |
| 74 | + |
| 75 | +```rust |
| 76 | +impl Foo { |
| 77 | + fn initialize(this: &mut MaybeUninit<Self>) { |
| 78 | + let bar: &mut MaybeUninit<i32> = this->bar; |
| 79 | + bar.write(42); |
| 80 | + } |
| 81 | +} |
| 82 | +``` |
| 83 | + |
| 84 | +There are a lot of types that can benefit from this operation: |
| 85 | +- `NonNull<T>` |
| 86 | +- `*const T`, `*mut T` |
| 87 | +- `&T`, `&mut T` |
| 88 | +- `&Cell<T>`, `&UnsafeCell<T>` |
| 89 | +- `&mut MaybeUninit<T>`, `*mut MaybeUninit<T>` |
| 90 | +- `cell::Ref<'_, T>`, `cell::RefMut<'_, T>` |
| 91 | +- `MappedMutexGuard<T>`, `MappedRwLockReadGuard<T>` and `MappedRwLockWriteGuard<T>` |
| 92 | + |
| 93 | +#### Pin Projections |
| 94 | + |
| 95 | +The examples from the previous section are very simple, since they all follow the pattern `C<T> -> |
| 96 | +C<F>` where `C` is the respective generic container type and `F` is a field of `T`. |
| 97 | + |
| 98 | +In order to handle `Pin<&mut T>`, the return type of the field projection operator needs to depend |
| 99 | +on the field itself. This is needed in order to be able to project structurally pinned fields from |
| 100 | +`Pin<&mut T>` to `Pin<&mut F1>` while simultaneously projecting not structurally pinned fields from |
| 101 | +`Pin<&mut T>` to `&mut F2`. |
| 102 | + |
| 103 | +Fields marked with `#[pin]` are structurally pinned field. For example, consider the following |
| 104 | +future: |
| 105 | + |
| 106 | +```rust |
| 107 | +struct FairRaceFuture<F1, F2> { |
| 108 | + #[pin] |
| 109 | + fut1: F1, |
| 110 | + #[pin] |
| 111 | + fut2: F2, |
| 112 | + fair: bool, |
| 113 | +} |
| 114 | +``` |
| 115 | + |
| 116 | +One can utilize the following projections when given `fut: Pin<&mut FairRaceFuture<F1, F2>>`: |
| 117 | +- `fut->fut1: Pin<&mut F1>` |
| 118 | +- `fut->fut2: Pin<&mut F2>` |
| 119 | +- `fut->fair: &mut bool` |
| 120 | + |
| 121 | +Using these, one can concisely implement `Future` for `FairRaceFuture`: |
| 122 | + |
| 123 | +```rust |
| 124 | +impl<F1: Future, F2: Future<Output = F1::Output>> Future for FairRaceFuture<F1, F2> { |
| 125 | + type Output = F1::Output; |
| 126 | + |
| 127 | + fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll<Self::Output> { |
| 128 | + let fair: &mut bool = self->fair; |
| 129 | + *fair = !*fair; |
| 130 | + if *fair { |
| 131 | + // self->fut1: Pin<&mut F1> |
| 132 | + match self->fut1.poll(ctx) { |
| 133 | + Poll::Pending => self->fut2.poll(ctx), |
| 134 | + Poll::Ready(res) => Poll::Ready(res), |
| 135 | + } |
| 136 | + } else { |
| 137 | + // self->fut2: Pin<&mut F2> |
| 138 | + match self->fut2.poll(ctx) { |
| 139 | + Poll::Pending => self->fut1.poll(ctx), |
| 140 | + Poll::Ready(res) => Poll::Ready(res), |
| 141 | + } |
| 142 | + } |
| 143 | + } |
| 144 | +} |
| 145 | +``` |
| 146 | + |
| 147 | +Without field projection, one would either have to use `unsafe` or reach for a third party library |
| 148 | +like [`pin-project`] or [`pin-project-lite`] and then use the provided `project` function. |
| 149 | + |
| 150 | +[`pin-project`]: https://crates.io/crates/pin-project |
| 151 | +[`pin-project-lite`]: https://crates.io/crates/pin-project-lite |
| 152 | + |
| 153 | +### The next 6 months |
| 154 | + |
| 155 | +#### Finish and accept the Field Projections RFC |
| 156 | + |
| 157 | +Solve big design questions using lang design meetings: |
| 158 | + |
| 159 | +- figure out the best design for field traits, |
| 160 | +- determine if `unsafe` field projections should exist, |
| 161 | +- settle on a design for the `Project` trait, |
| 162 | +- add support for simultaneous projections. |
| 163 | + |
| 164 | +Bikeshed/solve smaller issues: |
| 165 | + |
| 166 | +- projection operator syntax, |
| 167 | +- should naming field types have a native syntax? |
| 168 | +- naming of the different types and traits, |
| 169 | +- discuss which stdlib types should have field projection. |
| 170 | + |
| 171 | +#### Implement the RFC and Experiment |
| 172 | + |
| 173 | +- implement all of the various details from the RFC |
| 174 | +- experiment with field projections in the wild |
| 175 | +- iterate over the design using this experimentation |
| 176 | + |
| 177 | +### The "shiny future" we are working towards |
| 178 | + |
| 179 | +The ultimate goal is to have ergonomic field projections available in stable rust. Using it should |
| 180 | +feel similar to using field access today. |
| 181 | + |
| 182 | +## Ownership and team asks |
| 183 | + |
| 184 | +**Owner:** @y86-dev |
| 185 | + |
| 186 | +| Subgoal | Owner(s) or team(s) | Notes | |
| 187 | +| ---------------------------------------------- | -------------------------- | ----- | |
| 188 | +| Accept [Field Projections RFC] | | | |
| 189 | +| ↳ Design meeting | ![Team][] [lang] | | |
| 190 | +| ↳ RFC decisions | ![Team][] [lang] | | |
| 191 | +| Nightly Implementation for Field Projections | | | |
| 192 | +| ↳ Implementation | ![Help wanted][], @y86-dev | | |
| 193 | +| ↳ Standard reviews | ![Team][] [compiler] | | |
| 194 | + |
| 195 | +### Definitions |
| 196 | + |
| 197 | +Definitions for terms used above: |
| 198 | + |
| 199 | +* *Discussion and moral support* is the lowest level offering, basically committing the team to |
| 200 | + nothing but good vibes and general support for this endeavor. |
| 201 | +* *Author RFC* and *Implementation* means actually writing the code, document, whatever. |
| 202 | +* *Design meeting* means holding a synchronous meeting to review a proposal and provide feedback (no |
| 203 | + decision expected). |
| 204 | +* *RFC decisions* means reviewing an RFC and deciding whether to accept. |
| 205 | +* *Org decisions* means reaching a decision on an organizational or policy matter. |
| 206 | +* *Secondary review* of an RFC means that the team is "tangentially" involved in the RFC and should |
| 207 | + be expected to briefly review. |
| 208 | +* *Stabilizations* means reviewing a stabilization and report and deciding whether to stabilize. |
| 209 | +* *Standard reviews* refers to reviews for PRs against the repository; these PRs are not expected to |
| 210 | + be unduly large or complicated. |
| 211 | +* *Prioritized nominations* refers to prioritized lang-team response to nominated issues, with the |
| 212 | + expectation that there will be *some* response from the next weekly triage meeting. |
| 213 | +* *Dedicated review* means identifying an individual (or group of individuals) who will review the |
| 214 | + changes, as they're expected to require significant context. |
| 215 | +* Other kinds of decisions: |
| 216 | + * [Lang team experiments](https://lang-team.rust-lang.org/how_to/experiment.html) are used to |
| 217 | + add nightly features that do not yet have an RFC. They are limited to trusted contributors and |
| 218 | + are used to resolve design details such that an RFC can be written. |
| 219 | + * Compiler [Major Change Proposal (MCP)](https://forge.rust-lang.org/compiler/mcp.html) is used |
| 220 | + to propose a 'larger than average' change and get feedback from the compiler team. |
| 221 | + * Library [API Change Proposal |
| 222 | + (ACP)](https://std-dev-guide.rust-lang.org/development/feature-lifecycle.html) describes a |
| 223 | + change to the standard library. |
| 224 | + |
| 225 | +## Frequently asked questions |
| 226 | + |
| 227 | +### What do I do with this space? |
| 228 | + |
| 229 | +*This is a good place to elaborate on your reasoning above -- for example, why did you put the |
| 230 | +design axioms in the order that you did? It's also a good place to put the answers to any questions |
| 231 | +that come up during discussion. The expectation is that this FAQ section will grow as the goal is |
| 232 | +discussed and eventually should contain a complete summary of the points raised along the way.* |
0 commit comments