From 49addb3fdb351ab9a27425c9cfd051669d197fd1 Mon Sep 17 00:00:00 2001 From: y86-dev <94611769+y86-dev@users.noreply.github.com> Date: Tue, 13 Sep 2022 22:12:33 +0200 Subject: [PATCH 01/28] Initial draft --- text/0000-field-pojection.md | 475 +++++++++++++++++++++++++++++++++++ 1 file changed, 475 insertions(+) create mode 100644 text/0000-field-pojection.md diff --git a/text/0000-field-pojection.md b/text/0000-field-pojection.md new file mode 100644 index 00000000000..5315215d964 --- /dev/null +++ b/text/0000-field-pojection.md @@ -0,0 +1,475 @@ +- Feature Name: `field_projection` +- Start Date: 2022-09-10 +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Summary +[summary]: #summary + +The stdlib has wrapper types that impose some restrictions/additional features +on the types that are wrapped. For example: `MaybeUninit` allows `T` to be +partially initialized. These wrapper types also affect the fields of the types. +At the moment there is no easy access to these fields. +This RFC proposes to add field projection to certain wrapper types from the +stdlib: + +| from | to | +|-------------------------------------------------------|------------------------------------------------------| +|`&`[`MaybeUninit`][maybeuninit]`` |`&`[`MaybeUninit`][maybeuninit]`` | +|`&`[`Cell`][cell]`` |`&`[`Cell`][cell]`` | +|`&`[`UnsafeCell`][unsafecell]`` |`&`[`UnsafeCell`][unsafecell]`` | +|[`Option`][option]`<&Struct>` |[`Option`][option]`<&Field>` | +|[`Pin`][pin]`<&Struct>` |[`Pin`][pin]`<&Field>` | +|[`Pin`][pin]`<&`[`MaybeUninit`][maybeuninit]`>`|[`Pin`][pin]`<&`[`MaybeUninit`][maybeuninit]`>`| +|[`Ref`][ref]`<'_, Struct>` |[`Ref`][ref]`<'_, Field>` | + +As well as their mutable versions: + +| from | to | +|-----------------------------------------------------------|----------------------------------------------------------| +|`&mut` [`MaybeUninit`][maybeuninit]`` |`&mut` [`MaybeUninit`][maybeuninit]`` | +|`&mut` [`Cell`][cell]`` |`&mut` [`Cell`][cell]`` | +|`&mut` [`UnsafeCell`][unsafecell]`` |`&mut` [`UnsafeCell`][unsafecell]`` | +|[`Option`][option]`<&mut Struct>` |[`Option`][option]`<&mut Field>` | +|[`Pin`][pin]`<&mut Struct>` |[`Pin`][pin]`<&mut Field>` | +|[`Pin`][pin]`<&mut` [`MaybeUninit`][maybeuninit]`>`|[`Pin`][pin]`<&mut` [`MaybeUninit`][maybeuninit]`>`| +|[`RefMut`][refmut]`<'_, Struct>` |[`RefMut`][refmut]`<'_, Field>` | + +[maybeuninit]: https://doc.rust-lang.org/core/mem/union.MaybeUninit.html +[cell]: https://doc.rust-lang.org/core/cell/struct.Cell.html +[unsafecell]: https://doc.rust-lang.org/core/cell/struct.UnsafeCell.html +[option]: https://doc.rust-lang.org/core/option/enum.Option.html +[pin]: https://doc.rust-lang.org/core/pin/struct.Pin.html +[ref]: https://doc.rust-lang.org/core/cell/struct.Ref.html +[refmut]: https://doc.rust-lang.org/core/cell/struct.RefMut.html + +# Motivation +[motivation]: #motivation + +Currently, there are some map functions that provide this functionality. These +functions are not as ergonomic as a normal field access would be: +```rust +struct Count { + inner: usize, + outer: usize, +} +fn do_stuff(debug: Option<&mut Count>) { + // something that will be tracked by inner + if let Some(inner) = debug.map(|c| &mut c.inner) { + *inner += 1; + } + // something that will be tracked by outer + if let Some(outer) = debug.map(|c| &mut c.outer) { + *inner += 1; + } +} +``` +With this RFC this would become: +```rust +struct Count { + inner: usize, + outer: usize, +} +fn do_stuff(debug: Option<&mut Count>) { + // something that will be tracked by inner + if let Some(inner) = &mut debug.inner { + *inner += 1; + } + // something that will be tracked by outer + if let Some(outer) = &mut debug.outer { + *inner += 1; + } +} +``` +While this might only seem like a minor improvement for [`Option`][option]`` +it is transformative for [`Pin`][pin]`

` and +[`MaybeUninit`][maybeuninit]``: +```rust +struct Count { + inner: usize, + outer: usize, +} +fn init_count(mut count: Box>) -> Box { + let inner: &mut MaybeUninit = count.inner; + inner.write(42); + count.outer.write(63); + unsafe { + // SAFETY: all fields have been initialized + count.assume_init() // #![feature(new_uninit)] + } +} +``` +Before, this had to be done with raw pointers! +```rust +struct RaceFutures { + // Pin is somewhat special, it needs some way to specify + // structurally pinned fields, because `Pin<&mut T>` might + // not affect the whole of `T`. + #[pin] + fut1: F1, + #[pin] + fut2: F2, +} +impl Future for RaceFutures +where + F1: Future, + F2: Future, +{ + type Output = F1::Output; + + fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll { + match self.fut1.poll(ctx) { + Poll::Pending => self.fut2.poll(ctx), + rdy => rdy, + } + } +} +``` +Without this proposal, one would have to use `unsafe` with +`Pin::map_unchecked_mut` to project the inner fields. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +## [`MaybeUninit`][maybeuninit]`` + +When working with certain wrapper types in rust, you often want to access fields +of the wrapped types. When interfacing with C one often has to deal with +uninitialized data. In rust uninitialized data is represented by +[`MaybeUninit`][maybeuninit]``. In the following example we demonstrate +how one can initialize partial fields using [`MaybeUninit`][maybeuninit]``. +```rust +#[repr(C)] +pub struct MachineData { + incident_count: u32, + device_id: usize, + device_specific: *const core::ffi::c_void, +} + +extern "C" { + // provided by the C code + /// Initializes the `device_specific` pointer based on the value of `device_id`. + /// Returns -1 on error (unknown id) and 0 on success. + fn lookup_device_ptr(data: *mut MachineData) -> i32; +} + +pub struct UnknownId; + +impl MachineData { + pub fn new(id: usize) -> Result { + let mut this = MaybeUninit::::uninit(); + // the type of `this.device_id` is `MaybeUninit` + this.device_id.write(id); + this.incident_count.write(0); + // SAFETY: ffi-call, `device_id` has been initialized + if unsafe { lookup_device_ptr(this.as_mut_ptr()) } != 0 { + Err(UnknownId) + } else { + // SAFETY: all fields have been initialized + Ok(unsafe { this.assume_init() }) + } + } +} +``` +So to access a field of [`MaybeUninit`][maybeuninit]`` we can use +the already familiar syntax of accessing a field of `MachineData`/`&MachineData` +/`&mut MachineData`. The difference is that the type of the expression +`this.device_id` is now [`MaybeUninit`][maybeuninit]``. + +These *field projections* are also available on other types. + +## [`Pin`][pin]`

` projections + +Our second example is going to focus on [`Pin`][pin]`

`. This type is a little +special, as it allows unwrapping while projecting, but only for specific fields. +This information is expressed via the `#[pin]` attribute on the given field. +```rust +struct RaceFutures { + // we specify structurally pinned fields like this + #[pin] + fut1: F1, + #[pin] + fut2: F2, +} +impl Future for RaceFutures +where + F1: Future, + F2: Future, +{ + type Output = F1::Output; + + fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll { + // `self.fut1` has the type `Pin<&mut F1>` because `fut1` is a pinned field. + // if it was not pinned, the type would be `&mut F1`. + match self.fut1.poll(ctx) { + Poll::Pending => self.fut2.poll(ctx), + rdy => rdy, + } + } +} +``` + +## Cells + +When using [`Cell`][cell]`` or [`UnsafeCell`][cell]``, one can use the +same field access syntax as before to get a projected field: +```rust +struct Foo { + a: usize, + b: u64, +} + +fn process(x: &Cell, y: &Cell) { + x.a.swap(y.a); + x.b.set(x.b.get() + y.b.get()); +} +``` + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +See the tables from the [summary][summary] section for the exact projections that +are part of this RFC. + +## Implementation +### Trait-based + +It is currently unclear as to how this mechanism should be implemented. One +way would be to create a compiler-internal trait: +```rust +pub trait FieldProject { + type Wrapper<'a, T> + where + T: 'a; + type WrapperMut<'a, T> + where + T: 'a; + + /// Safety: closure must only do a field projection and not access the inner data + unsafe fn field_project<'a, T, U>( + this: Self::Wrapper<'a, T>, + f: impl FnOnce(*const T) -> *const U, + ) -> Self::Wrapper<'a, U>; + + /// Safety: closure must only do a field projection and not access the inner data + unsafe fn field_project_mut<'a, T, U>( + this: Self::WrapperMut<'a, T>, + f: impl FnOnce(*mut T) -> *mut U, + ) -> Self::WrapperMut<'a, U>; +} +``` + +That then would be implemented for the wrapper types. An example implementation +for [`MaybeUninit`][maybeuninit]``: +```rust +impl FieldProject for MaybeUninit<()> { + type Wrapper<'a, T> = &'a MaybeUninitwhere T:'a; + type WrapperMut<'a, T> = &'a mut MaybeUninitwhere T:'a; + + unsafe fn field_project<'a, T, U>( + this: Self::Wrapper<'a, T>, + f: impl FnOnce(*const T) -> *const U, + ) -> Self::Wrapper<'a, U> { + &*f(this.as_ptr()).cast::>() + } + + unsafe fn field_project_mut<'a, T, U>( + this: Self::WrapperMut<'a, T>, + f: impl FnOnce(*mut T) -> *mut U, + ) -> Self::WrapperMut<'a, U> { + &mut *f(this.as_mut_ptr()).cast::>() + } +} +``` +You can find the other implementations in [this](https://github.com/y86-dev/field-project) repository. + +### Others +It could also be entirely lang-item based. This would mean all wrapper types +would exhibit special field projection behavior. + +## Interactions with other language features + +### Bindings + +Bindings could also be supported: +```rust +struct Foo { + a: usize, + b: u64, +} + +fn process(x: &Cell, y: &Cell) { + let Foo { a: ax, b: bx } = x; + let Foo { a: ay, b: by } = y; + ax.swap(ay); + bx.set(bx.get() + by.get()); +} +``` + +This also enables support for `enum`s: +```rust +enum FooBar { + Foo(usize, usize), + Bar(usize), +} + +fn process(x: &Cell, y: &Cell) { + use FooBar::*; + match (x, y) { + (Foo(a, b), Foo(c, d)) => { + a.swap(c); + b.set(b.get() + d.get()); + } + (Bar(x), Bar(y)) => x.swap(y), + (Foo(a, b), Bar(y)) => a.swap(y), + (Bar(x), Foo(a, b)) => b.swap(x), + } +} +``` +They however seem not very compatible with [`MaybeUninit`][maybeuninit]`` +(more work needed). + +## Pin projections + +Because [`Pin`][pin]`

` is a bit special, as it is the only Wrapper that +permits access to raw fields when the user specifies so. It needs a mechanism +to do so. This proposal has chosen an attribute named `#[pin]` for this purpose. +It would only be a marker attribute and provide no functionality by itself. + +An additional challenge is that if a `!Unpin` field is marked `#[pin]`, then +one cannot implement the normal `Drop` trait, as it would give access to +`&mut self` even if `self` is pinned. Before this did not pose a problem, because +users would have to use `unsafe` to project `!Unpin` fields. But as this +proposal makes this possible, we have to account for this. + +The solution is similar to how [pin-project] solves this issue: Users are not +allowed to implement `Drop` manually, but instead can implement `PinnedDrop`: +```rust +pub trait PinnedDrop { + fn drop(self: Pin<&mut Self>); +} +``` +similar to `Drop::drop`, `PinnedDrop::drop` would not be callable by normal code. +The compiler would emit the following `Drop` stub for types that had `#[pin]`ned +fields and a user specified `PinnedDrop` impl: +```rust +impl Drop for $ty { + fn drop(&mut self) { + // SAFETY: because `self` is being dropped, there exists no other reference + // to it. Thus it will never move, if this function never moves it. + let this = unsafe { ::core::pin::Pin::new_unchecked(self) }; + ::drop(this) + } +} +``` + +# Drawbacks +[drawbacks]: #drawbacks + +- Users currently relying on crates that facilitate field projections (see +[prior art][prior-art]) will have to refactor their code. +- Increased compiler complexity: + - longer compile times + - potential worse type inference + + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +## Why is this design the best in the space of possible designs? +This design is most likely the simplest design for field-projections, as users +will use the same syntax they would use for field access. There is also no +ambiguity, because when fields can be projected, the fields would not be +accessible in the first place. + +One remaining issue is: how can users specify their own wrapper types? + +## What other designs have been considered and what is the rationale for not choosing them? + +This proposal was initially only designed to enable projecting +[`Pin`][pin]`<&mut T>`, because that would remove the need for `unsafe` when +pin projecting. + +It seems beneficial to also provide this functionality for a wider range of types. + +## What is the impact of not doing this? + +Users of these wrapper types need to rely on crates listed in [prior art][prior-art] +to provide sensible projections. Otherwise they can use the mapping functions +provided by some of the wrapper types. These are however, rather unergonomic +and wrappers like [`Pin`][pin]`

` require `unsafe`. + +# Prior art +[prior-art]: #prior-art + +## Crates + +There are some crates that enable field projections via (proc-)macros: + +- [pin-project] provides pin projections via a proc macro on the type specifying +the structurally pinned fields. At the projection-site the user calls a projection +function `.project()` and then receives a type with each field replaced with +the respective projected field. +- [field-project] provides pin/uninit projection via a macro at the projection-site: +the user writes `proj!($var.$field)` to project to `$field`. It works by +internally using `unsafe` and thus cannot pin-project `!Unpin` fields, because +that would be unsound due to the `Drop` impl a user could write. +- [cell-project] provides cell projection via a macro at the projection-site: +the user writes `cell_project!($ty, $val.$field)` where `$ty` is the type of `$val`. +Internally, it uses unsafe to facilitate the projection. +- [pin-projections] provides pin projections, it differs from [pin-project] by +providing explicit projection functions for each field. It also can generate +other types of getters for fields. [pin-project] seems like a more mature solution. +- [project-uninit] provides uninit projections via macros at the projection-site +uses `unsafe` internally. + +All of these crates have in common that their users have to use macros +when they want to perform a field projection. + +## Other languages + +I have done some quick research but have not found similar concepts in other +languages. C and C++ handle uninitialized memory differently by allowing +any memory to be uninitialized and thus a field projection to uninitialized +memory is just normal field access. These languages also do not have the wrapper +types that rust provides. + +## RFCs + +- [`ptr-to-field`](https://github.com/rust-lang/rfcs/pull/2708) + + +## Further discussion +- https://internals.rust-lang.org/t/cell-references-and-struct-layout/11564 + +[pin-project]: https://crates.io/crates/pin-project +[field-project]: https://crates.io/crates/field-project +[cell-project]: https://crates.io/crates/cell-project +[pin-projections]: https://crates.io/crates/pin-projections +[project-uninit]: https://crates.io/crates/project-uninit + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +## Before merging + +- Is new syntax for the borrowing necessary (e.g. `&pin mut x.y` or `&uninit mut x.y`)? +- Should there be a general mechanism to support nested projections? Currently +there is explicit support planned for [`Pin`][pin]`<&mut` [`MaybeUninit`][maybeuninit]`>`. + +## Before stabilization +- How can we enable users to leverage field projection? Maybe there should exist +a public trait that can be implemented to allow this. +- Should `union`s also be supported? +- How can `enum` and [`MaybeUninit`][maybeuninit]`` be made compatible? + +# Future possibilities +[future-possibilities]: #future-possibilities + +Even more generalized projections e.g. slices: At the moment + +- [`as_array_of_cells`](https://doc.rust-lang.org/core/cell/struct.Cell.html#method.as_array_of_cells) +- [`as_slice_of_cells`](https://doc.rust-lang.org/core/cell/struct.Cell.html#method.as_slice_of_cells) + +exist, maybe there is room for generalization here as well. + From 164bc222fbb76936135ff90afdb1fc08ffd4ac7c Mon Sep 17 00:00:00 2001 From: y86-dev <94611769+y86-dev@users.noreply.github.com> Date: Tue, 13 Sep 2022 23:47:00 +0200 Subject: [PATCH 02/28] Added Rc and Arc projection section in future possibilities --- text/0000-field-pojection.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/text/0000-field-pojection.md b/text/0000-field-pojection.md index 5315215d964..88092c2ea62 100644 --- a/text/0000-field-pojection.md +++ b/text/0000-field-pojection.md @@ -466,6 +466,8 @@ a public trait that can be implemented to allow this. # Future possibilities [future-possibilities]: #future-possibilities +## Arrays + Even more generalized projections e.g. slices: At the moment - [`as_array_of_cells`](https://doc.rust-lang.org/core/cell/struct.Cell.html#method.as_array_of_cells) @@ -473,3 +475,13 @@ Even more generalized projections e.g. slices: At the moment exist, maybe there is room for generalization here as well. +## [`Rc`]`` and [`Arc`]`` projections + +While out of scope for this RFC, projections for [`Rc`]`` and [`Arc`]`` +could be implemented in a similar way. This change seems to be a lot more +involved and will probably require that more information is stored in these +pointers. It seems more likely that this could be implemented for a new type +that explicitly opts in to provide field projections. + +[`Rc`]: https://doc.rust-lang.org/alloc/sync/struct.Rc.html +[`Arc`]: https://doc.rust-lang.org/alloc/sync/struct.Arc.html \ No newline at end of file From c6a0c2f0365ebc2400c79bb5de935a4efd7ac7b2 Mon Sep 17 00:00:00 2001 From: y86-dev <94611769+y86-dev@users.noreply.github.com> Date: Sat, 17 Sep 2022 00:46:10 +0200 Subject: [PATCH 03/28] Multiple changes: - Removed `Ref[Mut]` - clarified Pin and pin example - added attribute implementation details - projecting arc section - specified projectable pointers - elaborated on design rationale - updated unresolved questions --- text/0000-field-pojection.md | 309 +++++++++++++++++++++++------------ 1 file changed, 207 insertions(+), 102 deletions(-) diff --git a/text/0000-field-pojection.md b/text/0000-field-pojection.md index 88092c2ea62..14461c44452 100644 --- a/text/0000-field-pojection.md +++ b/text/0000-field-pojection.md @@ -21,19 +21,8 @@ stdlib: |[`Option`][option]`<&Struct>` |[`Option`][option]`<&Field>` | |[`Pin`][pin]`<&Struct>` |[`Pin`][pin]`<&Field>` | |[`Pin`][pin]`<&`[`MaybeUninit`][maybeuninit]`>`|[`Pin`][pin]`<&`[`MaybeUninit`][maybeuninit]`>`| -|[`Ref`][ref]`<'_, Struct>` |[`Ref`][ref]`<'_, Field>` | -As well as their mutable versions: - -| from | to | -|-----------------------------------------------------------|----------------------------------------------------------| -|`&mut` [`MaybeUninit`][maybeuninit]`` |`&mut` [`MaybeUninit`][maybeuninit]`` | -|`&mut` [`Cell`][cell]`` |`&mut` [`Cell`][cell]`` | -|`&mut` [`UnsafeCell`][unsafecell]`` |`&mut` [`UnsafeCell`][unsafecell]`` | -|[`Option`][option]`<&mut Struct>` |[`Option`][option]`<&mut Field>` | -|[`Pin`][pin]`<&mut Struct>` |[`Pin`][pin]`<&mut Field>` | -|[`Pin`][pin]`<&mut` [`MaybeUninit`][maybeuninit]`>`|[`Pin`][pin]`<&mut` [`MaybeUninit`][maybeuninit]`>`| -|[`RefMut`][refmut]`<'_, Struct>` |[`RefMut`][refmut]`<'_, Field>` | +Other pointers are also supported, for a list, see [here][supported-pointers]. [maybeuninit]: https://doc.rust-lang.org/core/mem/union.MaybeUninit.html [cell]: https://doc.rust-lang.org/core/cell/struct.Cell.html @@ -100,6 +89,8 @@ fn init_count(mut count: Box>) -> Box { } ``` Before, this had to be done with raw pointers! + +[`Pin`][pin]`

` has a similar story: ```rust struct RaceFutures { // Pin is somewhat special, it needs some way to specify @@ -128,6 +119,7 @@ where Without this proposal, one would have to use `unsafe` with `Pin::map_unchecked_mut` to project the inner fields. + # Guide-level explanation [guide-level-explanation]: #guide-level-explanation @@ -182,14 +174,14 @@ These *field projections* are also available on other types. Our second example is going to focus on [`Pin`][pin]`

`. This type is a little special, as it allows unwrapping while projecting, but only for specific fields. -This information is expressed via the `#[pin]` attribute on the given field. +This information is expressed via the `#[unpin]` attribute on the given field. ```rust struct RaceFutures { - // we specify structurally pinned fields like this - #[pin] fut1: F1, - #[pin] fut2: F2, + // this will be used to fairly poll the futures + #[unpin] + first: bool, } impl Future for RaceFutures where @@ -199,93 +191,162 @@ where type Output = F1::Output; fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll { - // `self.fut1` has the type `Pin<&mut F1>` because `fut1` is a pinned field. - // if it was not pinned, the type would be `&mut F1`. - match self.fut1.poll(ctx) { - Poll::Pending => self.fut2.poll(ctx), - rdy => rdy, - } + // we can access self.first mutably, because it is `#[unpin]` + self.first = !self.first; + if self.first { + // `self.fut1` has the type `Pin<&mut F1>` because `fut1` is a pinned field. + // if it was not pinned, the type would be `&mut F1`. + match self.fut1.poll(ctx) { + Poll::Pending => self.fut2.poll(ctx), + rdy => rdy, + } + } else { + match self.fut2.poll(ctx) { + Poll::Pending => self.fut1.poll(ctx), + rdy => rdy, + } + } } } ``` -## Cells +## Defining your own wrapper type + +First you need to decide what kind of projection your wrapper type needs: +- field projection: this allows users to project `&mut Wrapper` to `&mut Wrapper`, + this is only available on types with `#[repr(transparent)]` +- inner projection: this allows users to project `Wrapper<&mut Struct>` to `Wrapper<&mut Field>`, + this is *not* available for `union`s + -When using [`Cell`][cell]`` or [`UnsafeCell`][cell]``, one can use the -same field access syntax as before to get a projected field: +### Field projection +Annotate your type with `#[field_projecting($T)]` where `$T` is the +generic type parameter that you want to project. ```rust -struct Foo { - a: usize, - b: u64, +#[repr(transparent)] +#[field_projecting(T)] +pub union MaybeUninit { + uninit: (), + value: ManuallyDrop, } +``` -fn process(x: &Cell, y: &Cell) { - x.a.swap(y.a); - x.b.set(x.b.get() + y.b.get()); +### Inner projection +Annotate your type with `#[inner_projecting($T, $unwrap)]` where +- `$T` is the generic type parameter that you want to project. +- `$unwrap` is an optional identifier, that - when specified - is available to users to allow + projecting from `Wrapper> -> Pointer` on fields marked + with `#[$unwrap]`. +```rust +#[inner_projecting(T)] +pub enum Option { + Some(T), + None, +} +``` +Here is `Pin` as an example with `$unwrap`: +```rust +#[inner_projecting(T, unpin)] +pub struct Pin

{ + pointer: P, +} + +// now the user can write: +struct RaceFutures { + fut1: F1, + fut2: F2, + #[unpin] + first: bool, } ``` +`&mut race_future.first` has type `&mut bool`, because it is marked by `#[unpin]`. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -See the tables from the [summary][summary] section for the exact projections that -are part of this RFC. +Here is the list of types from `core` that will be `field_projecting`: -## Implementation -### Trait-based +- [`MaybeUninit`][maybeuninit] +- [`Cell`][cell] +- [`UnsafeCell`][unsafecell] + +These will be `inner_projecting`: + +- [`Option`][option] +- [`Pin`][pin] -It is currently unclear as to how this mechanism should be implemented. One -way would be to create a compiler-internal trait: +## Supported pointers +[supported-pointers]: #supported-pointers + +These are the pointer types that can be used as `P` in `P> -> +P>` for `field_projecting` and in `Wrapper> -> +Wrapper>` for `inner_projecting`: + +- `&mut T`, `&T` +- `*mut T`, `*const T`, `NonNull`, `AtomicPtr` +- `Pin

` where `P` is from above + +Note that all of these pointers have the same size and all can be transmuted to +and from `*mut T`. This is by design and other pointer types suggested should +follow this. There could be an internal trait ```rust -pub trait FieldProject { - type Wrapper<'a, T> - where - T: 'a; - type WrapperMut<'a, T> - where - T: 'a; - - /// Safety: closure must only do a field projection and not access the inner data - unsafe fn field_project<'a, T, U>( - this: Self::Wrapper<'a, T>, - f: impl FnOnce(*const T) -> *const U, - ) -> Self::Wrapper<'a, U>; - - /// Safety: closure must only do a field projection and not access the inner data - unsafe fn field_project_mut<'a, T, U>( - this: Self::WrapperMut<'a, T>, - f: impl FnOnce(*mut T) -> *mut U, - ) -> Self::WrapperMut<'a, U>; +trait NoMetadataPtr { + fn into_raw(self) -> *mut T; + + /// # Safety + /// The supplied `ptr` must have its origin from either `Self::into_raw`, or + /// be directly derived from it via field projection (`ptr::addr_of_mut!((*raw).field)`) + unsafe fn from_raw(ptr: *mut T) -> Self; } ``` +that then could be used by the compiler to ease the field projection +implementation. -That then would be implemented for the wrapper types. An example implementation -for [`MaybeUninit`][maybeuninit]``: -```rust -impl FieldProject for MaybeUninit<()> { - type Wrapper<'a, T> = &'a MaybeUninitwhere T:'a; - type WrapperMut<'a, T> = &'a mut MaybeUninitwhere T:'a; - - unsafe fn field_project<'a, T, U>( - this: Self::Wrapper<'a, T>, - f: impl FnOnce(*const T) -> *const U, - ) -> Self::Wrapper<'a, U> { - &*f(this.as_ptr()).cast::>() - } - unsafe fn field_project_mut<'a, T, U>( - this: Self::WrapperMut<'a, T>, - f: impl FnOnce(*mut T) -> *mut U, - ) -> Self::WrapperMut<'a, U> { - &mut *f(this.as_mut_ptr()).cast::>() - } +## Implementation + +Add two new attributes: `#[field_projecting($T)]` and `#[inner_projecting($T)]` +both taking a generic type parameter as an argument. + +### `#[field_projecting($T)]` + +#### Restrictions +This attribute is only allowed on `#[repr(transparent)]` types where the only +field has the layout of `$T`. Alternatively the type is a ZST. + +#### How it works +This is done, because to do a projection, the compiler will +`mem::transmute::<&mut Wrapper, *mut Struct>` and then get the field +using `ptr::addr_of_mut!` after which the pointer is then again +`mem::transmute::<*mut Field, &mut Wrapper>`d to yield the +projected field. + +### `#[inner_projecting($T, $unwrap)]` + +#### Restrictions +This attribute cannot be added on `union`s, because it is unclear what field +the projection would project. For example: +```rust +#[inner_projecting($T)] +pub union WeirdPair { + a: (ManuallyDrop, u32), + b: (u32, ManuallyDrop), } ``` -You can find the other implementations in [this](https://github.com/y86-dev/field-project) repository. -### Others -It could also be entirely lang-item based. This would mean all wrapper types -would exhibit special field projection behavior. +`$unwrap` can only be specified on `#[repr(transparent)]`, because otherwise +`Wrapper>` cannot be projected to `Pointer`. + + +#### How it works +Each field mentioning `$T` will either need to be a ZST, or `#[inner_projecting]` or `$T`. +The projection will work by projecting each field of type `Pointer<$T>` (remember, we +are projecting from `Wrapper> -> Wrapper>`) to +`Pointer<$F>` and construct a `Wrapper>` in place (because `Pointer<$F>` +will have the same size as `Pointer<$T>` this will take up the same number of +bytes, although the layout might be different). The last step will be skipped if +the field is marked with `#[$unwrap]`. + ## Interactions with other language features @@ -333,8 +394,17 @@ They however seem not very compatible with [`MaybeUninit`][maybeuninit]`` Because [`Pin`][pin]`

` is a bit special, as it is the only Wrapper that permits access to raw fields when the user specifies so. It needs a mechanism -to do so. This proposal has chosen an attribute named `#[pin]` for this purpose. +to do so. This proposal has chosen an attribute named `#[unpin]` for this purpose. It would only be a marker attribute and provide no functionality by itself. +It should be located either in the same module so `::core::pin::unpin` or at the +type itself `::core::pin::Pin::unpin`. + +There are several problems with choosing `#[unpin]` as the marker: +- poor migration support for users of [pin-project] +- not yet resolved the problem of `PinnedDrop` that can be implemented more + easily with `#[pin]`, see below. + +### Alternative: specify pinned fields instead (`#[pin]`) An additional challenge is that if a `!Unpin` field is marked `#[pin]`, then one cannot implement the normal `Drop` trait, as it would give access to @@ -363,6 +433,12 @@ impl Drop for $ty { } ``` +*To resolve before merge:* + +We could of course set an exception for `pin` and mark fields that keep the +wrapper in contrast to other types. But `Option` does not support projecting +"out of the wrapper" so this seems weird to make a general option. + # Drawbacks [drawbacks]: #drawbacks @@ -376,13 +452,41 @@ impl Drop for $ty { # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives -## Why is this design the best in the space of possible designs? -This design is most likely the simplest design for field-projections, as users -will use the same syntax they would use for field access. There is also no -ambiguity, because when fields can be projected, the fields would not be -accessible in the first place. +This RFC consciously chose the presented design, because it addresses the +following core issues: +- ergonomic field projection for a wide variety of types with user accesible + ways of implementing it for their own types. +- this feature integrates well with itself and other parts of the language. +- the field access operator `.` is not imbued with additional meaning: it does + not introduce overhead to use `.` on `&mut MaybeUninit` compared to `&mut T`. + +In particular this feature will not and *should not in the future* support +projecting types that require additional maintenance like `Arc`. +This would change the meaning of `.` allowing implicit creations of potentially +as many `Arc`s as one writes `.`. + +## *Out of scope:* `Arc` projection +[out-of-scope-arc-projection]: #out-of-scope-arc-projection + +With the current design of `Arc` it is not possible to add field projection, +because the refcount lives directly adjacent to the data. Instead the stdlib should +include a new type of `Arc` (or `ProjectedArc`) that allows +projection via a `map` function: +```rust +pub struct ProjectedArc { + backing: Arc, + ptr: NonNull, +} -One remaining issue is: how can users specify their own wrapper types? +impl Arc { + pub fn project(&self, map: impl FnOnce(&T) -> &U) -> ProjectedArc { + ProjectedArc { + backing: self.clone(), + ptr: NonNull::from(map(&**self)), + } + } +} +``` ## What other designs have been considered and what is the rationale for not choosing them? @@ -428,11 +532,14 @@ when they want to perform a field projection. ## Other languages -I have done some quick research but have not found similar concepts in other -languages. C and C++ handle uninitialized memory differently by allowing -any memory to be uninitialized and thus a field projection to uninitialized -memory is just normal field access. These languages also do not have the wrapper -types that rust provides. +Other languages generally do not have this feature in the same extend. C++ has +`shared_ptr` which allows the creation of another `shared_ptr` pointing at a field of +a `shared_ptr`'s pointee. This is possible, because `shared_ptr` is made up of +two pointers, one pointing to the data and another pointing at the ref count. +While this is not possible to add to `Arc` without introducing a new field, it +could be possible to add another `Arc` pointer that allowed field projections. +See [this section][out-of-scope-arc-projection] for more, as this is out of this RFC's +scope. ## RFCs @@ -453,15 +560,13 @@ types that rust provides. ## Before merging -- Is new syntax for the borrowing necessary (e.g. `&pin mut x.y` or `&uninit mut x.y`)? -- Should there be a general mechanism to support nested projections? Currently -there is explicit support planned for [`Pin`][pin]`<&mut` [`MaybeUninit`][maybeuninit]`>`. +[ ] Is new syntax for the borrowing necessary (e.g. `&pin mut x.y` or `&uninit mut x.y`)? ## Before stabilization -- How can we enable users to leverage field projection? Maybe there should exist +[x] How can we enable users to leverage field projection? Maybe there should exist a public trait that can be implemented to allow this. -- Should `union`s also be supported? -- How can `enum` and [`MaybeUninit`][maybeuninit]`` be made compatible? +[ ] Should `union`s also be supported? +[ ] How can `enum` and [`MaybeUninit`][maybeuninit]`` be made compatible? # Future possibilities [future-possibilities]: #future-possibilities @@ -478,10 +583,10 @@ exist, maybe there is room for generalization here as well. ## [`Rc`]`` and [`Arc`]`` projections While out of scope for this RFC, projections for [`Rc`]`` and [`Arc`]`` -could be implemented in a similar way. This change seems to be a lot more -involved and will probably require that more information is stored in these -pointers. It seems more likely that this could be implemented for a new type -that explicitly opts in to provide field projections. +could be implemented by adding another field that points to the ref count. +This RFC is designed for low cost projections, modifying an atomic ref count is +too slow to let it happen without explicit opt-in by the programmer and as such +it would be better to implement it via a dedicated `map` function. [`Rc`]: https://doc.rust-lang.org/alloc/sync/struct.Rc.html -[`Arc`]: https://doc.rust-lang.org/alloc/sync/struct.Arc.html \ No newline at end of file +[`Arc`]: https://doc.rust-lang.org/alloc/sync/struct.Arc.html From 606485778bea40045bb3fdf97928f8ec00439a09 Mon Sep 17 00:00:00 2001 From: y86-dev <94611769+y86-dev@users.noreply.github.com> Date: Sat, 17 Sep 2022 09:48:39 +0200 Subject: [PATCH 04/28] Fixed task boxes and added field disambiguation question --- text/0000-field-pojection.md | 75 +++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/text/0000-field-pojection.md b/text/0000-field-pojection.md index 14461c44452..a5721167de3 100644 --- a/text/0000-field-pojection.md +++ b/text/0000-field-pojection.md @@ -179,9 +179,9 @@ This information is expressed via the `#[unpin]` attribute on the given field. struct RaceFutures { fut1: F1, fut2: F2, - // this will be used to fairly poll the futures - #[unpin] - first: bool, + // this will be used to fairly poll the futures + #[unpin] + first: bool, } impl Future for RaceFutures where @@ -191,21 +191,21 @@ where type Output = F1::Output; fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll { - // we can access self.first mutably, because it is `#[unpin]` - self.first = !self.first; - if self.first { - // `self.fut1` has the type `Pin<&mut F1>` because `fut1` is a pinned field. - // if it was not pinned, the type would be `&mut F1`. - match self.fut1.poll(ctx) { - Poll::Pending => self.fut2.poll(ctx), - rdy => rdy, - } - } else { - match self.fut2.poll(ctx) { - Poll::Pending => self.fut1.poll(ctx), - rdy => rdy, - } - } + // we can access self.first mutably, because it is `#[unpin]` + self.first = !self.first; + if self.first { + // `self.fut1` has the type `Pin<&mut F1>` because `fut1` is a pinned field. + // if it was not pinned, the type would be `&mut F1`. + match self.fut1.poll(ctx) { + Poll::Pending => self.fut2.poll(ctx), + rdy => rdy, + } + } else { + match self.fut2.poll(ctx) { + Poll::Pending => self.fut1.poll(ctx), + rdy => rdy, + } + } } } ``` @@ -226,8 +226,8 @@ generic type parameter that you want to project. #[repr(transparent)] #[field_projecting(T)] pub union MaybeUninit { - uninit: (), - value: ManuallyDrop, + uninit: (), + value: ManuallyDrop, } ``` @@ -240,23 +240,23 @@ Annotate your type with `#[inner_projecting($T, $unwrap)]` where ```rust #[inner_projecting(T)] pub enum Option { - Some(T), - None, + Some(T), + None, } ``` Here is `Pin` as an example with `$unwrap`: ```rust #[inner_projecting(T, unpin)] pub struct Pin

{ - pointer: P, + pointer: P, } // now the user can write: struct RaceFutures { fut1: F1, fut2: F2, - #[unpin] - first: bool, + #[unpin] + first: bool, } ``` `&mut race_future.first` has type `&mut bool`, because it is marked by `#[unpin]`. @@ -291,12 +291,12 @@ and from `*mut T`. This is by design and other pointer types suggested should follow this. There could be an internal trait ```rust trait NoMetadataPtr { - fn into_raw(self) -> *mut T; + fn into_raw(self) -> *mut T; - /// # Safety - /// The supplied `ptr` must have its origin from either `Self::into_raw`, or - /// be directly derived from it via field projection (`ptr::addr_of_mut!((*raw).field)`) - unsafe fn from_raw(ptr: *mut T) -> Self; + /// # Safety + /// The supplied `ptr` must have its origin from either `Self::into_raw`, or + /// be directly derived from it via field projection (`ptr::addr_of_mut!((*raw).field)`) + unsafe fn from_raw(ptr: *mut T) -> Self; } ``` that then could be used by the compiler to ease the field projection @@ -329,8 +329,8 @@ the projection would project. For example: ```rust #[inner_projecting($T)] pub union WeirdPair { - a: (ManuallyDrop, u32), - b: (u32, ManuallyDrop), + a: (ManuallyDrop, u32), + b: (u32, ManuallyDrop), } ``` @@ -560,13 +560,16 @@ scope. ## Before merging -[ ] Is new syntax for the borrowing necessary (e.g. `&pin mut x.y` or `&uninit mut x.y`)? +- [ ] Is new syntax for the borrowing necessary (e.g. `&pin mut x.y` or `&uninit mut x.y`)? +- [ ] how do we disambiguate field access when both the wrapper and the struct + have the same named field? [`MaybeUninit`][maybeuninit]`.value` and `Struct` also + has `.value`. ## Before stabilization -[x] How can we enable users to leverage field projection? Maybe there should exist +- [x] How can we enable users to leverage field projection? Maybe there should exist a public trait that can be implemented to allow this. -[ ] Should `union`s also be supported? -[ ] How can `enum` and [`MaybeUninit`][maybeuninit]`` be made compatible? +- [ ] Should `union`s also be supported? +- [ ] How can `enum` and [`MaybeUninit`][maybeuninit]`` be made compatible? # Future possibilities [future-possibilities]: #future-possibilities From 220913781da87be13fe74beef1bf3752c938b032 Mon Sep 17 00:00:00 2001 From: y86-dev <94611769+y86-dev@users.noreply.github.com> Date: Wed, 21 Sep 2022 13:38:38 +0200 Subject: [PATCH 05/28] Added Deref clarification and small fixes --- text/0000-field-pojection.md | 71 ++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/text/0000-field-pojection.md b/text/0000-field-pojection.md index a5721167de3..c9f5fb2f47b 100644 --- a/text/0000-field-pojection.md +++ b/text/0000-field-pojection.md @@ -24,6 +24,33 @@ stdlib: Other pointers are also supported, for a list, see [here][supported-pointers]. +The projection works exactly like current field access: +```rust +struct MyStruct { + foo: Foo, + bar: usize, +} +struct Foo { + count: usize, +} +``` +when `mystruct` is of type `MyStruct`/`&mut MyStruct` field access works like this: + +| expression | type | +|-------------------------|-------------| +|`mystruct.foo` | `Foo` | +|`&mystruct.foo` | `&Foo` | +|`&mut mystruct.foo.count`|`&mut usize` | + +when `mystruct` is of type `&mut MaybeUninit` this proposal allows this: + +| expression | type | +|-------------------------|--------------------------| +|`mystruct.foo` | `MaybeUninit` | +|`&mystruct.foo` | `&MaybeUninit` | +|`&mut mystruct.foo.count`|`&mut MaybeUninit` | + + [maybeuninit]: https://doc.rust-lang.org/core/mem/union.MaybeUninit.html [cell]: https://doc.rust-lang.org/core/cell/struct.Cell.html [unsafecell]: https://doc.rust-lang.org/core/cell/struct.UnsafeCell.html @@ -390,6 +417,38 @@ fn process(x: &Cell, y: &Cell) { They however seem not very compatible with [`MaybeUninit`][maybeuninit]`` (more work needed). +### `Deref` and `DerefMut` + +Field projection should have higher priority similar to how field access has a higher priority than +`Deref`: +```rust +struct Foo { + field: usize, + inner: Bar, +} + +struct Bar { + field: isize, +} + +impl core::ops::Deref for Foo { + type Target = Bar; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +fn demo(f: &Foo) { + let _: usize = f.field; + let _: isize = (**f).field; +} +``` + +Users will have to explicitly deref the expression/call `deref` explicitly. + +This could potentially introduce code breakage, but only if the wrapper type implements `Deref`, +which is only the case for `Pin`. There is more information needed here. + ## Pin projections Because [`Pin`][pin]`

` is a bit special, as it is the only Wrapper that @@ -439,6 +498,7 @@ We could of course set an exception for `pin` and mark fields that keep the wrapper in contrast to other types. But `Option` does not support projecting "out of the wrapper" so this seems weird to make a general option. + # Drawbacks [drawbacks]: #drawbacks @@ -561,15 +621,18 @@ scope. ## Before merging - [ ] Is new syntax for the borrowing necessary (e.g. `&pin mut x.y` or `&uninit mut x.y`)? -- [ ] how do we disambiguate field access when both the wrapper and the struct - have the same named field? [`MaybeUninit`][maybeuninit]`.value` and `Struct` also - has `.value`. ## Before stabilization - [x] How can we enable users to leverage field projection? Maybe there should exist a public trait that can be implemented to allow this. - [ ] Should `union`s also be supported? - [ ] How can `enum` and [`MaybeUninit`][maybeuninit]`` be made compatible? +- [ ] for `Pin`, should we use `#[unpin]` like other `#[inner_projecting]`, or should we stick with `#[pin]` (and maybe introduce a way to switch between the two modes). +- [ ] how special does `PinnedDrop` need to be? This also ties in with the previous point, with `#[pin]` it is very easy to warrant a `PinnedDrop` instead of `Drop` (that will need to be compiler magic). With `#[unpin]` I do not really see a way how it could be implemented. +- [ ] Any new syntax? *I am leaning towards NO (except for the next point).* +- [ ] Disambiguate member access could we do something like `.value`? +- [ ] Should we expose the `NoMetadataPtr` to the user? +- [ ] What types should we also support? I am thinking of `PhantomData<&mut T>`, because this seems helpful in e.g. macro contexts that want to know the type of a field. # Future possibilities [future-possibilities]: #future-possibilities @@ -581,7 +644,7 @@ Even more generalized projections e.g. slices: At the moment - [`as_array_of_cells`](https://doc.rust-lang.org/core/cell/struct.Cell.html#method.as_array_of_cells) - [`as_slice_of_cells`](https://doc.rust-lang.org/core/cell/struct.Cell.html#method.as_slice_of_cells) -exist, maybe there is room for generalization here as well. +exist, maybe there is room for generalization there as well. ## [`Rc`]`` and [`Arc`]`` projections From ad53687322994b2116fae318c0314e4adcd17f57 Mon Sep 17 00:00:00 2001 From: y86-dev <94611769+y86-dev@users.noreply.github.com> Date: Wed, 21 Sep 2022 13:43:26 +0200 Subject: [PATCH 06/28] Updated pull request number --- text/{0000-field-pojection.md => 3318-field-pojection.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename text/{0000-field-pojection.md => 3318-field-pojection.md} (99%) diff --git a/text/0000-field-pojection.md b/text/3318-field-pojection.md similarity index 99% rename from text/0000-field-pojection.md rename to text/3318-field-pojection.md index c9f5fb2f47b..07a145c0f0b 100644 --- a/text/0000-field-pojection.md +++ b/text/3318-field-pojection.md @@ -1,6 +1,6 @@ - Feature Name: `field_projection` - Start Date: 2022-09-10 -- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- RFC PR: [rust-lang/rfcs#3318](https://github.com/rust-lang/rfcs/pull/3318) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) # Summary From ece8b0d8d19416780ba204e2356371cd52791797 Mon Sep 17 00:00:00 2001 From: y86-dev Date: Fri, 23 Sep 2022 13:44:21 +0200 Subject: [PATCH 07/28] Changed `Pin` projection implementation due to unsoundness hole also made small corrections --- text/3318-field-pojection.md | 232 ++++++++++++++++++++++++----------- 1 file changed, 160 insertions(+), 72 deletions(-) diff --git a/text/3318-field-pojection.md b/text/3318-field-pojection.md index 07a145c0f0b..dd3323ff460 100644 --- a/text/3318-field-pojection.md +++ b/text/3318-field-pojection.md @@ -6,12 +6,12 @@ # Summary [summary]: #summary -The stdlib has wrapper types that impose some restrictions/additional features +The std-lib has wrapper types that impose some restrictions/additional features on the types that are wrapped. For example: `MaybeUninit` allows `T` to be partially initialized. These wrapper types also affect the fields of the types. At the moment there is no easy access to these fields. This RFC proposes to add field projection to certain wrapper types from the -stdlib: +std-lib: | from | to | |-------------------------------------------------------|------------------------------------------------------| @@ -98,8 +98,7 @@ fn do_stuff(debug: Option<&mut Count>) { } ``` While this might only seem like a minor improvement for [`Option`][option]`` -it is transformative for [`Pin`][pin]`

` and -[`MaybeUninit`][maybeuninit]``: +it is transformative for [`Pin`][pin]`

` and [`MaybeUninit`][maybeuninit]``: ```rust struct Count { inner: usize, @@ -156,7 +155,7 @@ When working with certain wrapper types in rust, you often want to access fields of the wrapped types. When interfacing with C one often has to deal with uninitialized data. In rust uninitialized data is represented by [`MaybeUninit`][maybeuninit]``. In the following example we demonstrate -how one can initialize partial fields using [`MaybeUninit`][maybeuninit]``. +how one can initialize fields using [`MaybeUninit`][maybeuninit]``. ```rust #[repr(C)] pub struct MachineData { @@ -201,10 +200,15 @@ These *field projections* are also available on other types. Our second example is going to focus on [`Pin`][pin]`

`. This type is a little special, as it allows unwrapping while projecting, but only for specific fields. -This information is expressed via the `#[unpin]` attribute on the given field. +This information is expressed via the `#[pin]` attribute on each structurally pinned field. +The `#[unpin]` attribute enables *pin unwrapping*. Fields with no attributes will not be accessible +via field projections (remember that `Pin<&T>` implements `Deref` for *all* types `T`). ```rust +use core::pin::pin; struct RaceFutures { + #[pin] fut1: F1, + #[pin] fut2: F2, // this will be used to fairly poll the futures #[unpin] @@ -218,7 +222,7 @@ where type Output = F1::Output; fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll { - // we can access self.first mutably, because it is `#[unpin]` + // we can access self.first mutably, because it is not `#[pin]` self.first = !self.first; if self.first { // `self.fut1` has the type `Pin<&mut F1>` because `fut1` is a pinned field. @@ -259,11 +263,8 @@ pub union MaybeUninit { ``` ### Inner projection -Annotate your type with `#[inner_projecting($T, $unwrap)]` where -- `$T` is the generic type parameter that you want to project. -- `$unwrap` is an optional identifier, that - when specified - is available to users to allow - projecting from `Wrapper> -> Pointer` on fields marked - with `#[$unwrap]`. +Annotate your type with `#[inner_projecting($T)]` where `$T` is the generic type parameter +that you want to project. ```rust #[inner_projecting(T)] pub enum Option { @@ -271,22 +272,6 @@ pub enum Option { None, } ``` -Here is `Pin` as an example with `$unwrap`: -```rust -#[inner_projecting(T, unpin)] -pub struct Pin

{ - pointer: P, -} - -// now the user can write: -struct RaceFutures { - fut1: F1, - fut2: F2, - #[unpin] - first: bool, -} -``` -`&mut race_future.first` has type `&mut bool`, because it is marked by `#[unpin]`. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation @@ -341,6 +326,14 @@ both taking a generic type parameter as an argument. This attribute is only allowed on `#[repr(transparent)]` types where the only field has the layout of `$T`. Alternatively the type is a ZST. +```rust +#[field_projecting(T)] +pub struct Twice { +// ^^^^^^^^ error: field projecting type needs to be `#[repr(transparent)]` + field: T, +} +``` + #### How it works This is done, because to do a projection, the compiler will `mem::transmute::<&mut Wrapper, *mut Struct>` and then get the field @@ -348,7 +341,7 @@ using `ptr::addr_of_mut!` after which the pointer is then again `mem::transmute::<*mut Field, &mut Wrapper>`d to yield the projected field. -### `#[inner_projecting($T, $unwrap)]` +### `#[inner_projecting($T)]` #### Restrictions This attribute cannot be added on `union`s, because it is unclear what field @@ -360,26 +353,22 @@ pub union WeirdPair { b: (u32, ManuallyDrop), } ``` - -`$unwrap` can only be specified on `#[repr(transparent)]`, because otherwise -`Wrapper>` cannot be projected to `Pointer`. - +Each field mentioning `$T` will either need to be a ZST, `#[inner_projecting]` or `$T`. #### How it works -Each field mentioning `$T` will either need to be a ZST, or `#[inner_projecting]` or `$T`. -The projection will work by projecting each field of type `Pointer<$T>` (remember, we -are projecting from `Wrapper> -> Wrapper>`) to +First each field of type `Pointer<$T>` (remember, we are projecting from +`Wrapper> -> Wrapper>`) is projected to `Pointer<$F>` and construct a `Wrapper>` in place (because `Pointer<$F>` will have the same size as `Pointer<$T>` this will take up the same number of -bytes, although the layout might be different). The last step will be skipped if -the field is marked with `#[$unwrap]`. +bytes, although the layout might be different). +The special behavior of `Pin` is explained [below][pin-projections-section]. ## Interactions with other language features ### Bindings -Bindings could also be supported: +Bindings are also be supported: ```rust struct Foo { a: usize, @@ -389,33 +378,32 @@ struct Foo { fn process(x: &Cell, y: &Cell) { let Foo { a: ax, b: bx } = x; let Foo { a: ay, b: by } = y; + // ax, bx, ay and by are all &Cell; ax.swap(ay); bx.set(bx.get() + by.get()); } ``` - -This also enables support for `enum`s: +Enum bindings cannot be supported with the wrappers [`MaybeUninit`][maybeuninit]`` and +[`Cell`][cell]``: ```rust enum FooBar { Foo(usize, usize), Bar(usize), } -fn process(x: &Cell, y: &Cell) { - use FooBar::*; - match (x, y) { - (Foo(a, b), Foo(c, d)) => { - a.swap(c); - b.set(b.get() + d.get()); +fn problem(foo: &Cell) { + match foo { + Foo(a, b) => { + foo.set(Bar(0)); + // UB: access to uninhabited field! + let x = b.get(); } - (Bar(x), Bar(y)) => x.swap(y), - (Foo(a, b), Bar(y)) => a.swap(y), - (Bar(x), Foo(a, b)) => b.swap(x), + _ => {} } } ``` -They however seem not very compatible with [`MaybeUninit`][maybeuninit]`` -(more work needed). +[`MaybeUninit`][maybeuninit]`` has the problem that we cannot read the discriminant (as it might +not be initialized). ### `Deref` and `DerefMut` @@ -446,24 +434,86 @@ fn demo(f: &Foo) { Users will have to explicitly deref the expression/call `deref` explicitly. -This could potentially introduce code breakage, but only if the wrapper type implements `Deref`, -which is only the case for `Pin`. There is more information needed here. +This does not introduces code breakage, because `Pin` is the only wrapper type that is `Deref`. +And it special treatment will be explained below. ## Pin projections +[pin-projections-section]: #pin-projections + +Because [`Pin`][pin]`

` is a bit special, as it is the only Wrapper that permits access to raw +fields when the user specifies so. It needs a mechanism to do so. This proposal has chosen two +attributes named `#[pin]` and `#[unpin]` for this purpose. They are marker attribute and provide no +further functionality. They will be located at `::core::pin::{pin, unpin}`. + +In a future edition they can be added into the prelude. + +Two attributes are required for backwards compatibility. Suppose there exists this library: +```rust +pub struct BufPtr { + buf: [u8; 64], + // null, or points into buf + ptr: *const u8, + // for some legacy reasons this is `pub` and cannot be changed + pub len: u8, + _pin: PhantomPinned, +} + +impl BufPtr { + pub fn new(buf: [u8; 64]) -> Self { + Self { + buf, + ptr: ptr::null(), + len: 0, + _pin: PhantomPinned, + } + } + + pub fn next(self: Pin<&mut Self>) -> Option { + // SAFETY: we do not move out of `this` + let this = unsafe { Pin::get_unchecked_mut(self) }; + if this.ptr.is_null() { + let buf: *const [u8] = &this.buf[1..]; + this.ptr = buf.cast(); + // here we set the len and it cannot be changed, since `self` is `!Unpin` and it is pinned. + this.len = 1; + Some(this.buf[0]) + } else if this.len >= 64 { + None + } else { + this.len += 1; + // SAFETY: `ptr` is not null, so it points into `buf` + let res = Some(unsafe { *this.ptr }); + this.ptr = this.ptr.wrapping_add(1); + res + } + } +} +``` +The code is sound, because after pinning `BufPtr`, users cannot change `len` with safe code. -Because [`Pin`][pin]`

` is a bit special, as it is the only Wrapper that -permits access to raw fields when the user specifies so. It needs a mechanism -to do so. This proposal has chosen an attribute named `#[unpin]` for this purpose. -It would only be a marker attribute and provide no functionality by itself. -It should be located either in the same module so `::core::pin::unpin` or at the -type itself `::core::pin::Pin::unpin`. +If this proposal would treat all fields without `#[pin]` as `#[unpin]` then this would be possible: +```rust +fn main() { + let mut buf = Box::pin(BufPtr::new([0; 64])); + loop { + // field projection used here: + buf.as_mut().len = 0; + buf.as_mut().next(); // at some point we read some bad address + } +} +``` -There are several problems with choosing `#[unpin]` as the marker: -- poor migration support for users of [pin-project] -- not yet resolved the problem of `PinnedDrop` that can be implemented more - easily with `#[pin]`, see below. +That is why for `Pin` we need the default behavior of **no projection at all**. +Users can specify unwrapping/projecting with `#[unpin]`/`#[pin]`. -### Alternative: specify pinned fields instead (`#[pin]`) +Marking an `Unpin` field `#[pin]` produces a warning: +```rust +struct MyStruct { + #[pin] + // ^^^^^^ warning pinning `u64` is useless, because it implements `Unpin` [read here for more information]. + num: u64, +} +``` An additional challenge is that if a `!Unpin` field is marked `#[pin]`, then one cannot implement the normal `Drop` trait, as it would give access to @@ -492,12 +542,26 @@ impl Drop for $ty { } ``` -*To resolve before merge:* +An error is emitted if a `Drop` impl is found when at least one field is marked `#[pin]`: +```rust +struct MyStruct { + buf: [u8; 64], + ptr: *const u8, + #[pin] + _pin: PhantomPinned, +} -We could of course set an exception for `pin` and mark fields that keep the -wrapper in contrast to other types. But `Option` does not support projecting -"out of the wrapper" so this seems weird to make a general option. +impl Drop for MyStruct { +// ^^^^ error: cannot implement `Drop` for `MyStruct` because it has pinned fields. +// help: implement `PinnedDrop` instead + fn drop(&mut self) { + println!("Dropping MyStruct"); + } +} +``` +`PinnedDrop` would also reside in `core::ops` and should be added to the prelude in the next +edition. # Drawbacks [drawbacks]: #drawbacks @@ -507,6 +571,7 @@ wrapper in contrast to other types. But `Option` does not support projecting - Increased compiler complexity: - longer compile times - potential worse type inference + - `Pin` projection support might be confusing to new users # Rationale and alternatives @@ -514,7 +579,7 @@ wrapper in contrast to other types. But `Option` does not support projecting This RFC consciously chose the presented design, because it addresses the following core issues: -- ergonomic field projection for a wide variety of types with user accesible +- ergonomic field projection for a wide variety of types with user accessible ways of implementing it for their own types. - this feature integrates well with itself and other parts of the language. - the field access operator `.` is not imbued with additional meaning: it does @@ -529,7 +594,7 @@ as many `Arc`s as one writes `.`. [out-of-scope-arc-projection]: #out-of-scope-arc-projection With the current design of `Arc` it is not possible to add field projection, -because the refcount lives directly adjacent to the data. Instead the stdlib should +because the ref-count lives directly adjacent to the data. Instead the std-lib should include a new type of `Arc` (or `ProjectedArc`) that allows projection via a `map` function: ```rust @@ -556,6 +621,28 @@ pin projecting. It seems beneficial to also provide this functionality for a wider range of types. +### Using solely `#[pin]`/`#[unpin]` for specifying structurally pinned fields + +Because it makes existing code unsound this option has not been chosen. + +### Trait instead of attributes to create a wrapper type + +The trait could look like this: +```rust +pub trait FieldProjecting { + type Inner; + unsafe fn project(self, proj: impl FnOnce(*mut T) -> *mut U) -> Self::Inner; +} +``` +Implementing this trait is a footgun. One has to use raw pointers only and already know a good bit +about `unsafe` code to write this correctly. This also opens the door for implementations that do +not abide by the "no additional maintenance" invariant. + +It could of course just be a marker trait and fulfill the same purpose as the attributes. That would +enable using the condition "this type has projection" as type bounds. But this marker trait could +also be added later. + + ## What is the impact of not doing this? Users of these wrapper types need to rely on crates listed in [prior art][prior-art] @@ -626,13 +713,14 @@ scope. - [x] How can we enable users to leverage field projection? Maybe there should exist a public trait that can be implemented to allow this. - [ ] Should `union`s also be supported? -- [ ] How can `enum` and [`MaybeUninit`][maybeuninit]`` be made compatible? +- [ ] ~~How can `enum` and [`MaybeUninit`][maybeuninit]`` be made compatible?~~ - [ ] for `Pin`, should we use `#[unpin]` like other `#[inner_projecting]`, or should we stick with `#[pin]` (and maybe introduce a way to switch between the two modes). - [ ] how special does `PinnedDrop` need to be? This also ties in with the previous point, with `#[pin]` it is very easy to warrant a `PinnedDrop` instead of `Drop` (that will need to be compiler magic). With `#[unpin]` I do not really see a way how it could be implemented. - [ ] Any new syntax? *I am leaning towards NO (except for the next point).* - [ ] Disambiguate member access could we do something like `.value`? - [ ] Should we expose the `NoMetadataPtr` to the user? - [ ] What types should we also support? I am thinking of `PhantomData<&mut T>`, because this seems helpful in e.g. macro contexts that want to know the type of a field. +- [ ] should the warning when using `#[pin]` on an `Unpin` field be an error? # Future possibilities [future-possibilities]: #future-possibilities From 516ced8d68b8370a1d9ed285d981d2b6dc7b34c6 Mon Sep 17 00:00:00 2001 From: y86-dev Date: Sat, 24 Sep 2022 10:34:36 +0200 Subject: [PATCH 08/28] Added personal bias to unresolved questions --- text/3318-field-pojection.md | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/text/3318-field-pojection.md b/text/3318-field-pojection.md index dd3323ff460..ebcb6624b27 100644 --- a/text/3318-field-pojection.md +++ b/text/3318-field-pojection.md @@ -705,22 +705,26 @@ scope. # Unresolved questions [unresolved-questions]: #unresolved-questions +You can find the direction I am currently biased towards at the end of each Question. `(Y) = Yes`, +`(N) = No`, `(-) = no bias`. Questions that have been crossed out are no longer relevant/have been +answered with "No". + ## Before merging -- [ ] Is new syntax for the borrowing necessary (e.g. `&pin mut x.y` or `&uninit mut x.y`)? +- [ ] Is new syntax for the borrowing necessary (e.g. `&pin mut x.y` or `&uninit mut x.y`)? `(N)` ## Before stabilization - [x] How can we enable users to leverage field projection? Maybe there should exist -a public trait that can be implemented to allow this. -- [ ] Should `union`s also be supported? -- [ ] ~~How can `enum` and [`MaybeUninit`][maybeuninit]`` be made compatible?~~ -- [ ] for `Pin`, should we use `#[unpin]` like other `#[inner_projecting]`, or should we stick with `#[pin]` (and maybe introduce a way to switch between the two modes). -- [ ] how special does `PinnedDrop` need to be? This also ties in with the previous point, with `#[pin]` it is very easy to warrant a `PinnedDrop` instead of `Drop` (that will need to be compiler magic). With `#[unpin]` I do not really see a way how it could be implemented. -- [ ] Any new syntax? *I am leaning towards NO (except for the next point).* -- [ ] Disambiguate member access could we do something like `.value`? -- [ ] Should we expose the `NoMetadataPtr` to the user? -- [ ] What types should we also support? I am thinking of `PhantomData<&mut T>`, because this seems helpful in e.g. macro contexts that want to know the type of a field. -- [ ] should the warning when using `#[pin]` on an `Unpin` field be an error? +a public trait that can be implemented to allow this. `(Y)` +- [ ] Should `union`s also be supported? `(N)` +- [ ] How can `enum` and [`MaybeUninit`][maybeuninit]`` be made compatible? `(N)` +- [ ] ~~for `Pin`, should we use `#[unpin]` like other `#[inner_projecting]`, or should we stick with `#[pin]` (and maybe introduce a way to switch between the two modes).~~ +- [ ] ~~how special does `PinnedDrop` need to be? This also ties in with the previous point, with `#[pin]` it is very easy to warrant a `PinnedDrop` instead of `Drop` (that will need to be compiler magic). With `#[unpin]` I do not really see a way how it could be implemented.~~ +- [ ] Any new syntax? `(N)` (except the next point) +- [ ] Disambiguate member access could we do something like `.value`? `(Y)` +- [ ] Should we expose the `NoMetadataPtr` to the user? `(-)` +- [ ] What types should we also support? I am thinking of `PhantomData<&mut T>`, because this seems helpful in e.g. macro contexts that want to know the type of a field. `(Y)` +- [ ] should the warning when using `#[pin]` on an `Unpin` field be an error? `(Y)` # Future possibilities [future-possibilities]: #future-possibilities From 1deacbd1e8b249bec0d9d49ea50914dd9a8fe563 Mon Sep 17 00:00:00 2001 From: y86-dev Date: Thu, 29 Sep 2022 23:39:50 +0200 Subject: [PATCH 09/28] Fixed FieldProjecting trait and added impl example --- text/3318-field-pojection.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/text/3318-field-pojection.md b/text/3318-field-pojection.md index ebcb6624b27..27789f87c30 100644 --- a/text/3318-field-pojection.md +++ b/text/3318-field-pojection.md @@ -629,8 +629,9 @@ Because it makes existing code unsound this option has not been chosen. The trait could look like this: ```rust -pub trait FieldProjecting { - type Inner; +pub unsafe trait FieldProjecting<'a, T: 'a> where Self: 'a { + type Inner: 'a; + unsafe fn project(self, proj: impl FnOnce(*mut T) -> *mut U) -> Self::Inner; } ``` @@ -642,6 +643,25 @@ It could of course just be a marker trait and fulfill the same purpose as the at enable using the condition "this type has projection" as type bounds. But this marker trait could also be added later. +Example implementations for [`MaybeUninit`][maybeuninit] and [`Pin`][pin]: +```rust +unsafe impl<'a, T> FieldProjecting<'a, T> for &'a mut MaybeUninit { + type Inner = &'a mut MaybeUninit; + + unsafe fn project(self, proj: impl FnOnce(*mut T) -> *mut U) -> Self::Inner { + &mut *proj(self.as_mut_ptr()).cast::>() + } +} + +unsafe impl<'a, T> FieldProjecting<'a, T> for Pin<&'a mut T> { + type Inner = Pin<&'a mut U>; + + unsafe fn project(self, proj: impl FnOnce(*mut T) -> *mut U) -> Self::Inner { + Pin::new_unchecked(&mut *proj(addr_of_mut!(*Pin::into_inner_unchecked(self)))) + } +} +``` + ## What is the impact of not doing this? From f15137511e911bc2a9e487a2b362ae82ad68cdcc Mon Sep 17 00:00:00 2001 From: y86-dev Date: Thu, 29 Sep 2022 23:55:48 +0200 Subject: [PATCH 10/28] Moved `Option` into future possibilities --- text/3318-field-pojection.md | 79 ++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/text/3318-field-pojection.md b/text/3318-field-pojection.md index 27789f87c30..177d4a6b33c 100644 --- a/text/3318-field-pojection.md +++ b/text/3318-field-pojection.md @@ -18,7 +18,6 @@ std-lib: |`&`[`MaybeUninit`][maybeuninit]`` |`&`[`MaybeUninit`][maybeuninit]`` | |`&`[`Cell`][cell]`` |`&`[`Cell`][cell]`` | |`&`[`UnsafeCell`][unsafecell]`` |`&`[`UnsafeCell`][unsafecell]`` | -|[`Option`][option]`<&Struct>` |[`Option`][option]`<&Field>` | |[`Pin`][pin]`<&Struct>` |[`Pin`][pin]`<&Field>` | |[`Pin`][pin]`<&`[`MaybeUninit`][maybeuninit]`>`|[`Pin`][pin]`<&`[`MaybeUninit`][maybeuninit]`>`| @@ -63,42 +62,26 @@ when `mystruct` is of type `&mut MaybeUninit` this proposal allows thi [motivation]: #motivation Currently, there are some map functions that provide this functionality. These -functions are not as ergonomic as a normal field access would be: +functions are not as ergonomic as a normal field access would be. Accessing the fields can also be a +totally safe operation, but the wrapper mapping functions need to be marked `unsafe`. This results +in poor api ergonomics: ```rust struct Count { inner: usize, outer: usize, } -fn do_stuff(debug: Option<&mut Count>) { - // something that will be tracked by inner - if let Some(inner) = debug.map(|c| &mut c.inner) { - *inner += 1; - } - // something that will be tracked by outer - if let Some(outer) = debug.map(|c| &mut c.outer) { - *inner += 1; - } -} -``` -With this RFC this would become: -```rust -struct Count { - inner: usize, - outer: usize, -} -fn do_stuff(debug: Option<&mut Count>) { - // something that will be tracked by inner - if let Some(inner) = &mut debug.inner { - *inner += 1; - } - // something that will be tracked by outer - if let Some(outer) = &mut debug.outer { - *inner += 1; +fn init_count(mut count: Box>) -> Box { + let inner: &mut MaybeUninit = + unsafe { &mut *addr_of_mut!((*count.as_mut_ptr()).inner).cast::>() }; + inner.write(42); + unsafe { &mut *addr_of_mut!((*count.as_mut_ptr()).outer).cast::>() }.write(63); + unsafe { + // SAFETY: all fields have been initialized + count.assume_init() // #![feature(new_uninit)] } } ``` -While this might only seem like a minor improvement for [`Option`][option]`` -it is transformative for [`Pin`][pin]`

` and [`MaybeUninit`][maybeuninit]``: +Using the proposal from this RFC, the code simplifies to this: ```rust struct Count { inner: usize, @@ -114,10 +97,29 @@ fn init_count(mut count: Box>) -> Box { } } ``` -Before, this had to be done with raw pointers! - [`Pin`][pin]`

` has a similar story: ```rust +struct RaceFutures { + fut1: F1, + fut2: F2, +} +impl Future for RaceFutures +where + F1: Future, + F2: Future, +{ + type Output = F1::Output; + + fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll { + match unsafe { self.map_unchecked_mut(|t| &mut t.fut1) }.poll(ctx) { + Poll::Pending => unsafe { self.map_unchecked_mut(|t| &mut t.fut2) }.poll(ctx), + rdy => rdy, + } + } +} +``` +It gets a lot simpler: +```rust struct RaceFutures { // Pin is somewhat special, it needs some way to specify // structurally pinned fields, because `Pin<&mut T>` might @@ -142,9 +144,6 @@ where } } ``` -Without this proposal, one would have to use `unsafe` with -`Pin::map_unchecked_mut` to project the inner fields. - # Guide-level explanation [guide-level-explanation]: #guide-level-explanation @@ -266,10 +265,9 @@ pub union MaybeUninit { Annotate your type with `#[inner_projecting($T)]` where `$T` is the generic type parameter that you want to project. ```rust -#[inner_projecting(T)] -pub enum Option { - Some(T), - None, +#[inner_projecting(P)] +pub struct Pin

{ + pointer: P } ``` @@ -284,7 +282,6 @@ Here is the list of types from `core` that will be `field_projecting`: These will be `inner_projecting`: -- [`Option`][option] - [`Pin`][pin] ## Supported pointers @@ -749,6 +746,10 @@ a public trait that can be implemented to allow this. `(Y)` # Future possibilities [future-possibilities]: #future-possibilities +## Types that could benefit from projections + +- [`Option`][option], it would be `#[inner_projecting(T)]`, so it allows projecting from `Option>` to `Option>` + ## Arrays Even more generalized projections e.g. slices: At the moment From 3aa007de5caae3b83f834996a85ce06190106637 Mon Sep 17 00:00:00 2001 From: y86-dev Date: Thu, 29 Sep 2022 23:56:58 +0200 Subject: [PATCH 11/28] Added PhantomData to future possibilities --- text/3318-field-pojection.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/3318-field-pojection.md b/text/3318-field-pojection.md index 177d4a6b33c..82f0ce9941e 100644 --- a/text/3318-field-pojection.md +++ b/text/3318-field-pojection.md @@ -740,7 +740,7 @@ a public trait that can be implemented to allow this. `(Y)` - [ ] Any new syntax? `(N)` (except the next point) - [ ] Disambiguate member access could we do something like `.value`? `(Y)` - [ ] Should we expose the `NoMetadataPtr` to the user? `(-)` -- [ ] What types should we also support? I am thinking of `PhantomData<&mut T>`, because this seems helpful in e.g. macro contexts that want to know the type of a field. `(Y)` +- [ ] What types should we also support? - [ ] should the warning when using `#[pin]` on an `Unpin` field be an error? `(Y)` # Future possibilities @@ -749,6 +749,7 @@ a public trait that can be implemented to allow this. `(Y)` ## Types that could benefit from projections - [`Option`][option], it would be `#[inner_projecting(T)]`, so it allows projecting from `Option>` to `Option>` +- [`PhantomData`], it could allow macros to better access the type of a field. ## Arrays @@ -769,3 +770,4 @@ it would be better to implement it via a dedicated `map` function. [`Rc`]: https://doc.rust-lang.org/alloc/sync/struct.Rc.html [`Arc`]: https://doc.rust-lang.org/alloc/sync/struct.Arc.html +[`PhantomData`]: https://doc.rust-lang.org/core/marker/struct.PhantomData.html From 95d1e856b9f8632e94e767bc34b74f4a1f95ce75 Mon Sep 17 00:00:00 2001 From: y86-dev Date: Fri, 30 Sep 2022 20:57:08 +0200 Subject: [PATCH 12/28] Added secion about potential operator `~` --- text/3318-field-pojection.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/text/3318-field-pojection.md b/text/3318-field-pojection.md index 82f0ce9941e..1b4378d3c91 100644 --- a/text/3318-field-pojection.md +++ b/text/3318-field-pojection.md @@ -610,6 +610,33 @@ impl Arc { } ``` +## Create a field projection operator + +In [Gankra's blog post][faultlore] about overhauling raw pointers she talks about using `~` as field +projection for raw pointers. + +We could implement field projection for raw pointers with the current approach, but that would +result in `x.y`. If we instead adopt Gankra's idea for field projection in general, it would also +more clearly convey the intent. + +### Advantages: + +- we can imbue `~` with new meaning, users will not assume anything about what the operator does + from other languages +- it clearly differentiates it from normal field access + +### Disadvantages: + +- `.` is less confusing to beginners compared to `~`. Other languages use it primarly as a unary + binary negation operator +- `.` can be used on `&mut Struct`, `&Struct` and `Struct`. The first two are outliers, as they do + not have fields themselves (`.` is actually `(*expr).field`). So it would be weird to make + references special and not also require `~` for them. We could still allow `a~b` to be a shorthand + for `&mut a.b`. +- migrating from `pin-project` is going to be a *lot* tougher. users will have to change every + access via `.` to `~` compared to just having to remove `.project`. + + ## What other designs have been considered and what is the rationale for not choosing them? This proposal was initially only designed to enable projecting @@ -771,3 +798,4 @@ it would be better to implement it via a dedicated `map` function. [`Rc`]: https://doc.rust-lang.org/alloc/sync/struct.Rc.html [`Arc`]: https://doc.rust-lang.org/alloc/sync/struct.Arc.html [`PhantomData`]: https://doc.rust-lang.org/core/marker/struct.PhantomData.html +[faultlore]: https://faultlore.com/blah/fix-rust-pointers/ From 11e2cadb62f334f4c04c4b2dd8ac186dc2d7e5be Mon Sep 17 00:00:00 2001 From: y86-dev Date: Fri, 30 Sep 2022 21:00:30 +0200 Subject: [PATCH 13/28] Added safety clarification --- text/3318-field-pojection.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/text/3318-field-pojection.md b/text/3318-field-pojection.md index 1b4378d3c91..f513059b76b 100644 --- a/text/3318-field-pojection.md +++ b/text/3318-field-pojection.md @@ -145,6 +145,10 @@ where } ``` +It is the most important goal of this RFC to make field projection an entirely safe operation. It +also tries to make supporting field projection a safe operation. This second point is not as +important as the first, but it will make field projection more accesible in third party libraries. + # Guide-level explanation [guide-level-explanation]: #guide-level-explanation From 1c8d12746a22f4177949e7b3dc2708ea3e37e5dc Mon Sep 17 00:00:00 2001 From: y86-dev Date: Fri, 30 Sep 2022 21:01:14 +0200 Subject: [PATCH 14/28] spelling --- text/3318-field-pojection.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/3318-field-pojection.md b/text/3318-field-pojection.md index f513059b76b..4908b7f8fda 100644 --- a/text/3318-field-pojection.md +++ b/text/3318-field-pojection.md @@ -64,7 +64,7 @@ when `mystruct` is of type `&mut MaybeUninit` this proposal allows thi Currently, there are some map functions that provide this functionality. These functions are not as ergonomic as a normal field access would be. Accessing the fields can also be a totally safe operation, but the wrapper mapping functions need to be marked `unsafe`. This results -in poor api ergonomics: +in poor API ergonomics: ```rust struct Count { inner: usize, @@ -147,7 +147,7 @@ where It is the most important goal of this RFC to make field projection an entirely safe operation. It also tries to make supporting field projection a safe operation. This second point is not as -important as the first, but it will make field projection more accesible in third party libraries. +important as the first, but it will make field projection more accessible in third party libraries. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation @@ -631,7 +631,7 @@ more clearly convey the intent. ### Disadvantages: -- `.` is less confusing to beginners compared to `~`. Other languages use it primarly as a unary +- `.` is less confusing to beginners compared to `~`. Other languages use it primarily as a unary binary negation operator - `.` can be used on `&mut Struct`, `&Struct` and `Struct`. The first two are outliers, as they do not have fields themselves (`.` is actually `(*expr).field`). So it would be weird to make From eebcb9a7d969424265da774498f835ecd3affae0 Mon Sep 17 00:00:00 2001 From: y86-dev Date: Sat, 1 Oct 2022 15:15:16 +0200 Subject: [PATCH 15/28] Fixed auto-deref of `.` --- text/3318-field-pojection.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/text/3318-field-pojection.md b/text/3318-field-pojection.md index 4908b7f8fda..790f6c77914 100644 --- a/text/3318-field-pojection.md +++ b/text/3318-field-pojection.md @@ -627,16 +627,12 @@ more clearly convey the intent. - we can imbue `~` with new meaning, users will not assume anything about what the operator does from other languages -- it clearly differentiates it from normal field access +- it clearly differentiates it from normal field access (and will not have auto-deref like `.`) ### Disadvantages: - `.` is less confusing to beginners compared to `~`. Other languages use it primarily as a unary binary negation operator -- `.` can be used on `&mut Struct`, `&Struct` and `Struct`. The first two are outliers, as they do - not have fields themselves (`.` is actually `(*expr).field`). So it would be weird to make - references special and not also require `~` for them. We could still allow `a~b` to be a shorthand - for `&mut a.b`. - migrating from `pin-project` is going to be a *lot* tougher. users will have to change every access via `.` to `~` compared to just having to remove `.project`. From 20d5f0b8eb4d36f752273476c2a2dce1eb49d9de Mon Sep 17 00:00:00 2001 From: y86-dev Date: Sat, 15 Oct 2022 23:28:39 +0200 Subject: [PATCH 16/28] fixed file name typo --- text/{3318-field-pojection.md => 3318-field-projection.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename text/{3318-field-pojection.md => 3318-field-projection.md} (100%) diff --git a/text/3318-field-pojection.md b/text/3318-field-projection.md similarity index 100% rename from text/3318-field-pojection.md rename to text/3318-field-projection.md From 04df98a10ffa97b9d5574602ff9b9dc8a959611d Mon Sep 17 00:00:00 2001 From: y86-dev Date: Mon, 7 Nov 2022 19:33:08 +0100 Subject: [PATCH 17/28] Complete reworking of the implementation section --- text/3318-field-projection.md | 558 +++++++++++++--------------------- 1 file changed, 203 insertions(+), 355 deletions(-) diff --git a/text/3318-field-projection.md b/text/3318-field-projection.md index 790f6c77914..b3f57f1cf27 100644 --- a/text/3318-field-projection.md +++ b/text/3318-field-projection.md @@ -23,7 +23,9 @@ std-lib: Other pointers are also supported, for a list, see [here][supported-pointers]. -The projection works exactly like current field access: +Projection is facilitated by an operator and the syntax looks similar to normal field access. This +operator can be a new one (e.g. `~` or `->`) or it could be overloading `.`. This RFC will use `->` +as a placeholder for future bikeshedding. ```rust struct MyStruct { foo: Foo, @@ -45,9 +47,9 @@ when `mystruct` is of type `&mut MaybeUninit` this proposal allows thi | expression | type | |-------------------------|--------------------------| -|`mystruct.foo` | `MaybeUninit` | -|`&mystruct.foo` | `&MaybeUninit` | -|`&mut mystruct.foo.count`|`&mut MaybeUninit` | +|`mystruct->foo` | `MaybeUninit` | +|`mystruct->foo` | `&MaybeUninit` | +|`mystruct->foo->count`|`&mut MaybeUninit` | [maybeuninit]: https://doc.rust-lang.org/core/mem/union.MaybeUninit.html @@ -60,8 +62,12 @@ when `mystruct` is of type `&mut MaybeUninit` this proposal allows thi # Motivation [motivation]: #motivation +There are situations that necessitate heavy usage of wrapper types instead of their underlying +pointers. In the Linux kernel for example many types need to be pinned, because they contain self +referential datastructures. This results in `Pin` being present almost everywhere. Thus pin +projections are required instead of normal accesses. -Currently, there are some map functions that provide this functionality. These +Currently, there are mapping functions that provide this functionality. These functions are not as ergonomic as a normal field access would be. Accessing the fields can also be a totally safe operation, but the wrapper mapping functions need to be marked `unsafe`. This results in poor API ergonomics: @@ -88,9 +94,9 @@ struct Count { outer: usize, } fn init_count(mut count: Box>) -> Box { - let inner: &mut MaybeUninit = count.inner; + let inner: &mut MaybeUninit = count->inner; inner.write(42); - count.outer.write(63); + count->outer.write(63); unsafe { // SAFETY: all fields have been initialized count.assume_init() // #![feature(new_uninit)] @@ -137,26 +143,23 @@ where type Output = F1::Output; fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll { - match self.fut1.poll(ctx) { - Poll::Pending => self.fut2.poll(ctx), + match self->fut1.poll(ctx) { + Poll::Pending => self->fut2.poll(ctx), rdy => rdy, } } } ``` - -It is the most important goal of this RFC to make field projection an entirely safe operation. It -also tries to make supporting field projection a safe operation. This second point is not as -important as the first, but it will make field projection more accessible in third party libraries. +It is the most important goal of this RFC to make field projection an ergonomic and safe operation. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation ## [`MaybeUninit`][maybeuninit]`` -When working with certain wrapper types in rust, you often want to access fields +When working with certain wrapper types in rust, one often want to access fields of the wrapped types. When interfacing with C one often has to deal with -uninitialized data. In rust uninitialized data is represented by +uninitialized data. In Rust, uninitialized data is represented by [`MaybeUninit`][maybeuninit]``. In the following example we demonstrate how one can initialize fields using [`MaybeUninit`][maybeuninit]``. ```rust @@ -179,9 +182,8 @@ pub struct UnknownId; impl MachineData { pub fn new(id: usize) -> Result { let mut this = MaybeUninit::::uninit(); - // the type of `this.device_id` is `MaybeUninit` - this.device_id.write(id); - this.incident_count.write(0); + this->device_id.write(id); + this->incident_count.write(0); // SAFETY: ffi-call, `device_id` has been initialized if unsafe { lookup_device_ptr(this.as_mut_ptr()) } != 0 { Err(UnknownId) @@ -192,10 +194,9 @@ impl MachineData { } } ``` -So to access a field of [`MaybeUninit`][maybeuninit]`` we can use -the already familiar syntax of accessing a field of `MachineData`/`&MachineData` -/`&mut MachineData`. The difference is that the type of the expression -`this.device_id` is now [`MaybeUninit`][maybeuninit]``. +So to access a field of [`MaybeUninit`][maybeuninit]`` we can use similar syntax to +accessing a field of `MachineData`. The difference is that the type of the expression +`this->device_id` is now [`MaybeUninit`][maybeuninit]``. These *field projections* are also available on other types. @@ -204,8 +205,8 @@ These *field projections* are also available on other types. Our second example is going to focus on [`Pin`][pin]`

`. This type is a little special, as it allows unwrapping while projecting, but only for specific fields. This information is expressed via the `#[pin]` attribute on each structurally pinned field. -The `#[unpin]` attribute enables *pin unwrapping*. Fields with no attributes will not be accessible -via field projections (remember that `Pin<&T>` implements `Deref` for *all* types `T`). +Untagged fields are *unwrapped* when projected. So the projection is `Pin<&mut Struct> -> &mut +Field`. ```rust use core::pin::pin; struct RaceFutures { @@ -214,7 +215,6 @@ struct RaceFutures { #[pin] fut2: F2, // this will be used to fairly poll the futures - #[unpin] first: bool, } impl Future for RaceFutures @@ -226,17 +226,17 @@ where fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll { // we can access self.first mutably, because it is not `#[pin]` - self.first = !self.first; - if self.first { - // `self.fut1` has the type `Pin<&mut F1>` because `fut1` is a pinned field. + *self->first = !*self->first; + if *self->first { + // `self->fut1` has the type `Pin<&mut F1>` because `fut1` is a pinned field. // if it was not pinned, the type would be `&mut F1`. - match self.fut1.poll(ctx) { - Poll::Pending => self.fut2.poll(ctx), + match self->fut1.poll(ctx) { + Poll::Pending => self->fut2.poll(ctx), rdy => rdy, } } else { - match self.fut2.poll(ctx) { - Poll::Pending => self.fut1.poll(ctx), + match self->fut2.poll(ctx) { + Poll::Pending => self->fut1.poll(ctx), rdy => rdy, } } @@ -246,278 +246,193 @@ where ## Defining your own wrapper type -First you need to decide what kind of projection your wrapper type needs: -- field projection: this allows users to project `&mut Wrapper` to `&mut Wrapper`, - this is only available on types with `#[repr(transparent)]` -- inner projection: this allows users to project `Wrapper<&mut Struct>` to `Wrapper<&mut Field>`, - this is *not* available for `union`s - - -### Field projection -Annotate your type with `#[field_projecting($T)]` where `$T` is the -generic type parameter that you want to project. +If you want to add field projection to a wrapper type, you will need to implement the `Project` +trait for the type that you want to project: ```rust #[repr(transparent)] -#[field_projecting(T)] -pub union MaybeUninit { - uninit: (), - value: ManuallyDrop, -} -``` +pub struct Wrapper(T); -### Inner projection -Annotate your type with `#[inner_projecting($T)]` where `$T` is the generic type parameter -that you want to project. -```rust -#[inner_projecting(P)] -pub struct Pin

{ - pointer: P +impl<'a, T> Project<'a> for &'a mut Wrapper +where + T: HasFields // this trait ensures that T actually has fields to project. +{ + // This type is the type that will be projected. + type Inner = T; + // This is the type resulting from a projection to a field of type U. + type Output = &'a mut Wrapper; + // This is the type resulting from unwrapping a field of type U. + type Unwrap = &'a mut U; + + unsafe fn project_true(self, field: Field) -> Self::Output { + // SAFETY: because Wrapper is repr(transparent), we can do the cast + // and because of field's invariants this results in the correct field pointer. + unsafe { + // we can project `*mut T` to `*mut U` using `project` (this is provided by the + // `Project` trait). + &mut *(&mut self.0 as *mut T).project(field).cast::>() + } + } + + unsafe fn unwrap_true(self, field: Field) -> Self::Unwrap { + // SAFETY: because Wrapper is repr(transparent), we can do the cast + // and because of field's invariants this results in the correct field pointer. + unsafe { + // we can project `*mut T` to `*mut U` using `project` (this is provided by the + // `Project` trait). + &mut *(&mut self.0 as *mut T).project(field) + } + } } ``` +Now anyone can project `&mut Wrapper -> &mut Wrapper` via `struct->field` if the +field is visible. Additionally the field needs to be projectable. This is expressed by the +`Projectable` trait. So the projection is only allowed, if `Field: Projectable<&mut Wrapper>`. +`N` is a compiler generated identifier of the specific field. This implementation needs to be +provided by the author of `Struct`. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -Here is the list of types from `core` that will be `field_projecting`: - -- [`MaybeUninit`][maybeuninit] -- [`Cell`][cell] -- [`UnsafeCell`][unsafecell] - -These will be `inner_projecting`: - -- [`Pin`][pin] - -## Supported pointers -[supported-pointers]: #supported-pointers - -These are the pointer types that can be used as `P` in `P> -> -P>` for `field_projecting` and in `Wrapper> -> -Wrapper>` for `inner_projecting`: - -- `&mut T`, `&T` -- `*mut T`, `*const T`, `NonNull`, `AtomicPtr` -- `Pin

` where `P` is from above - -Note that all of these pointers have the same size and all can be transmuted to -and from `*mut T`. This is by design and other pointer types suggested should -follow this. There could be an internal trait -```rust -trait NoMetadataPtr { - fn into_raw(self) -> *mut T; - - /// # Safety - /// The supplied `ptr` must have its origin from either `Self::into_raw`, or - /// be directly derived from it via field projection (`ptr::addr_of_mut!((*raw).field)`) - unsafe fn from_raw(ptr: *mut T) -> Self; -} -``` -that then could be used by the compiler to ease the field projection -implementation. - - -## Implementation - -Add two new attributes: `#[field_projecting($T)]` and `#[inner_projecting($T)]` -both taking a generic type parameter as an argument. - -### `#[field_projecting($T)]` - -#### Restrictions -This attribute is only allowed on `#[repr(transparent)]` types where the only -field has the layout of `$T`. Alternatively the type is a ZST. - +The following types will be added to `core`: ```rust -#[field_projecting(T)] -pub struct Twice { -// ^^^^^^^^ error: field projecting type needs to be `#[repr(transparent)]` - field: T, -} -``` - -#### How it works -This is done, because to do a projection, the compiler will -`mem::transmute::<&mut Wrapper, *mut Struct>` and then get the field -using `ptr::addr_of_mut!` after which the pointer is then again -`mem::transmute::<*mut Field, &mut Wrapper>`d to yield the -projected field. - -### `#[inner_projecting($T)]` - -#### Restrictions -This attribute cannot be added on `union`s, because it is unclear what field -the projection would project. For example: -```rust -#[inner_projecting($T)] -pub union WeirdPair { - a: (ManuallyDrop, u32), - b: (u32, ManuallyDrop), -} -``` -Each field mentioning `$T` will either need to be a ZST, `#[inner_projecting]` or `$T`. - -#### How it works -First each field of type `Pointer<$T>` (remember, we are projecting from -`Wrapper> -> Wrapper>`) is projected to -`Pointer<$F>` and construct a `Wrapper>` in place (because `Pointer<$F>` -will have the same size as `Pointer<$T>` this will take up the same number of -bytes, although the layout might be different). - -The special behavior of `Pin` is explained [below][pin-projections-section]. - -## Interactions with other language features +pub unsafe trait Project<'a>: 'a + Sized { + type Inner: 'a + HasFields; + type Projected: 'a + where + Self: 'a; + type Unwrapped: 'a + where + Self: 'a; + + fn project( + self, + field: Field, + ) -> < as Projectable<'a, Self>>::ProjKind as ProjSelector<'a, Self>>::Output + where + Field: Projectable<'a, Self>, + as Projectable<'a, Self>>::ProjKind: ProjSelector<'a, Self>, + { + unsafe { + as Projectable<'a, Self>>::ProjKind::select_proj(self, field) + } + } -### Bindings + unsafe fn project_field( + self, + field: Field, + ) -> Self::Projected + where + Field: Projectable<'a, Self>; -Bindings are also be supported: -```rust -struct Foo { - a: usize, - b: u64, + unsafe fn unwrap_field( + self, + field: Field, + ) -> Self::Unwrapped + where + Field: Projectable<'a, Self>; } -fn process(x: &Cell, y: &Cell) { - let Foo { a: ax, b: bx } = x; - let Foo { a: ay, b: by } = y; - // ax, bx, ay and by are all &Cell; - ax.swap(ay); - bx.set(bx.get() + by.get()); -} -``` -Enum bindings cannot be supported with the wrappers [`MaybeUninit`][maybeuninit]`` and -[`Cell`][cell]``: -```rust -enum FooBar { - Foo(usize, usize), - Bar(usize), +pub struct Field { + offset: usize, + phantom: PhantomData (T, U)>, } -fn problem(foo: &Cell) { - match foo { - Foo(a, b) => { - foo.set(Bar(0)); - // UB: access to uninhabited field! - let x = b.get(); +impl Field { + pub const unsafe fn new(offset: usize) -> Self { + Self { + offset, + phantom: PhantomData, } - _ => {} } -} -``` -[`MaybeUninit`][maybeuninit]`` has the problem that we cannot read the discriminant (as it might -not be initialized). -### `Deref` and `DerefMut` - -Field projection should have higher priority similar to how field access has a higher priority than -`Deref`: -```rust -struct Foo { - field: usize, - inner: Bar, + pub fn offset(&self) -> usize { + self.offset + } } -struct Bar { - field: isize, +pub struct Projected; +pub struct Unwrapped; + +mod sealed { + pub unsafe trait IsField {} + unsafe impl IsField for super::Field {} + + pub unsafe trait IsProjKind {} + unsafe impl IsProjKind for super::Projected {} + unsafe impl IsProjKind for super::Unwrapped {} +} + +pub unsafe trait ProjSelector<'a, P: Project<'a>>: sealed::IsProjKind { + type Output: 'a + where + Self: 'a; + unsafe fn select_proj( + proj: P, + field: Field, + ) -> Self::Output + where + Field: Projectable<'a, P>; +} + +unsafe impl<'a, P: Project<'a>> ProjSelector<'a, P> for Projected { + type Output = P::Projected; + unsafe fn select_proj( + proj: P, + field: Field, + ) -> Self::Output + where + Field: Projectable<'a, P>, + { + P::project_field(proj, field) + } } -impl core::ops::Deref for Foo { - type Target = Bar; - fn deref(&self) -> &Self::Target { - &self.inner +unsafe impl<'a, P: Project<'a>> ProjSelector<'a, P> for Unwrapped { + type Output = P::Unwrapped; + unsafe fn select_proj( + proj: P, + field: Field, + ) -> Self::Output + where + Field: Projectable<'a, P>, + { + P::unwrap_field(proj, field) } } -fn demo(f: &Foo) { - let _: usize = f.field; - let _: isize = (**f).field; +pub trait Projectable<'a, P: Project<'a>>: sealed::IsField { + type ProjKind: sealed::IsProjKind; } ``` +This design is very flexible for authors writing wrapper types. There is almost no restriction on +what can be projected. And this design includes direct support for unwrapping. -Users will have to explicitly deref the expression/call `deref` explicitly. +Here is the list of types from `core` that will have a projection implementation: -This does not introduces code breakage, because `Pin` is the only wrapper type that is `Deref`. -And it special treatment will be explained below. +- `&mut T`, `&T` +- `*mut T`, `*const T` +- [`&mut MaybeUninit`][maybeuninit] +- [`&Cell`][cell], [`&UnsafeCell`][unsafecell] +- [`Pin<&mut T>`][pin], `Pin<&mut MaybeUninit>` ## Pin projections [pin-projections-section]: #pin-projections -Because [`Pin`][pin]`

` is a bit special, as it is the only Wrapper that permits access to raw -fields when the user specifies so. It needs a mechanism to do so. This proposal has chosen two -attributes named `#[pin]` and `#[unpin]` for this purpose. They are marker attribute and provide no -further functionality. They will be located at `::core::pin::{pin, unpin}`. - -In a future edition they can be added into the prelude. - -Two attributes are required for backwards compatibility. Suppose there exists this library: -```rust -pub struct BufPtr { - buf: [u8; 64], - // null, or points into buf - ptr: *const u8, - // for some legacy reasons this is `pub` and cannot be changed - pub len: u8, - _pin: PhantomPinned, -} - -impl BufPtr { - pub fn new(buf: [u8; 64]) -> Self { - Self { - buf, - ptr: ptr::null(), - len: 0, - _pin: PhantomPinned, - } - } - - pub fn next(self: Pin<&mut Self>) -> Option { - // SAFETY: we do not move out of `this` - let this = unsafe { Pin::get_unchecked_mut(self) }; - if this.ptr.is_null() { - let buf: *const [u8] = &this.buf[1..]; - this.ptr = buf.cast(); - // here we set the len and it cannot be changed, since `self` is `!Unpin` and it is pinned. - this.len = 1; - Some(this.buf[0]) - } else if this.len >= 64 { - None - } else { - this.len += 1; - // SAFETY: `ptr` is not null, so it points into `buf` - let res = Some(unsafe { *this.ptr }); - this.ptr = this.ptr.wrapping_add(1); - res - } - } -} -``` -The code is sound, because after pinning `BufPtr`, users cannot change `len` with safe code. +Because [`Pin

`][pin] permits unwrapping when the user specifies so. There are multiple ways to +implement this: -If this proposal would treat all fields without `#[pin]` as `#[unpin]` then this would be possible: -```rust -fn main() { - let mut buf = Box::pin(BufPtr::new([0; 64])); - loop { - // field projection used here: - buf.as_mut().len = 0; - buf.as_mut().next(); // at some point we read some bad address - } -} -``` +- nothing, users can add their own implementations. +- introduce an attribute `#[pin]` and/or `#[unpin]` on fields that automatically add the respective + implementation. +- a derive/attribute/function-like macro that adds the implementations. -That is why for `Pin` we need the default behavior of **no projection at all**. -Users can specify unwrapping/projecting with `#[unpin]`/`#[pin]`. +This RFC is going to use `#[pin]` in the next section to refer to the method of marking a field +pin-projected. -Marking an `Unpin` field `#[pin]` produces a warning: -```rust -struct MyStruct { - #[pin] - // ^^^^^^ warning pinning `u64` is useless, because it implements `Unpin` [read here for more information]. - num: u64, -} -``` +### `PinnedDrop` An additional challenge is that if a `!Unpin` field is marked `#[pin]`, then -one cannot implement the normal `Drop` trait, as it would give access to +one cannot implement the normal `Drop` trait on the struct, as it would give access to `&mut self` even if `self` is pinned. Before this did not pose a problem, because users would have to use `unsafe` to project `!Unpin` fields. But as this proposal makes this possible, we have to account for this. @@ -582,37 +497,7 @@ This RFC consciously chose the presented design, because it addresses the following core issues: - ergonomic field projection for a wide variety of types with user accessible ways of implementing it for their own types. -- this feature integrates well with itself and other parts of the language. -- the field access operator `.` is not imbued with additional meaning: it does - not introduce overhead to use `.` on `&mut MaybeUninit` compared to `&mut T`. - -In particular this feature will not and *should not in the future* support -projecting types that require additional maintenance like `Arc`. -This would change the meaning of `.` allowing implicit creations of potentially -as many `Arc`s as one writes `.`. - -## *Out of scope:* `Arc` projection -[out-of-scope-arc-projection]: #out-of-scope-arc-projection - -With the current design of `Arc` it is not possible to add field projection, -because the ref-count lives directly adjacent to the data. Instead the std-lib should -include a new type of `Arc` (or `ProjectedArc`) that allows -projection via a `map` function: -```rust -pub struct ProjectedArc { - backing: Arc, - ptr: NonNull, -} -impl Arc { - pub fn project(&self, map: impl FnOnce(&T) -> &U) -> ProjectedArc { - ProjectedArc { - backing: self.clone(), - ptr: NonNull::from(map(&**self)), - } - } -} -``` ## Create a field projection operator @@ -649,44 +534,6 @@ It seems beneficial to also provide this functionality for a wider range of type Because it makes existing code unsound this option has not been chosen. -### Trait instead of attributes to create a wrapper type - -The trait could look like this: -```rust -pub unsafe trait FieldProjecting<'a, T: 'a> where Self: 'a { - type Inner: 'a; - - unsafe fn project(self, proj: impl FnOnce(*mut T) -> *mut U) -> Self::Inner; -} -``` -Implementing this trait is a footgun. One has to use raw pointers only and already know a good bit -about `unsafe` code to write this correctly. This also opens the door for implementations that do -not abide by the "no additional maintenance" invariant. - -It could of course just be a marker trait and fulfill the same purpose as the attributes. That would -enable using the condition "this type has projection" as type bounds. But this marker trait could -also be added later. - -Example implementations for [`MaybeUninit`][maybeuninit] and [`Pin`][pin]: -```rust -unsafe impl<'a, T> FieldProjecting<'a, T> for &'a mut MaybeUninit { - type Inner = &'a mut MaybeUninit; - - unsafe fn project(self, proj: impl FnOnce(*mut T) -> *mut U) -> Self::Inner { - &mut *proj(self.as_mut_ptr()).cast::>() - } -} - -unsafe impl<'a, T> FieldProjecting<'a, T> for Pin<&'a mut T> { - type Inner = Pin<&'a mut U>; - - unsafe fn project(self, proj: impl FnOnce(*mut T) -> *mut U) -> Self::Inner { - Pin::new_unchecked(&mut *proj(addr_of_mut!(*Pin::into_inner_unchecked(self)))) - } -} -``` - - ## What is the impact of not doing this? Users of these wrapper types need to rely on crates listed in [prior art][prior-art] @@ -729,8 +576,7 @@ a `shared_ptr`'s pointee. This is possible, because `shared_ptr` is made up of two pointers, one pointing to the data and another pointing at the ref count. While this is not possible to add to `Arc` without introducing a new field, it could be possible to add another `Arc` pointer that allowed field projections. -See [this section][out-of-scope-arc-projection] for more, as this is out of this RFC's -scope. +See [the future possibilities section][arc-projection] for more. ## RFCs @@ -753,21 +599,8 @@ You can find the direction I am currently biased towards at the end of each Ques `(N) = No`, `(-) = no bias`. Questions that have been crossed out are no longer relevant/have been answered with "No". -## Before merging - -- [ ] Is new syntax for the borrowing necessary (e.g. `&pin mut x.y` or `&uninit mut x.y`)? `(N)` - -## Before stabilization -- [x] How can we enable users to leverage field projection? Maybe there should exist -a public trait that can be implemented to allow this. `(Y)` - [ ] Should `union`s also be supported? `(N)` - [ ] How can `enum` and [`MaybeUninit`][maybeuninit]`` be made compatible? `(N)` -- [ ] ~~for `Pin`, should we use `#[unpin]` like other `#[inner_projecting]`, or should we stick with `#[pin]` (and maybe introduce a way to switch between the two modes).~~ -- [ ] ~~how special does `PinnedDrop` need to be? This also ties in with the previous point, with `#[pin]` it is very easy to warrant a `PinnedDrop` instead of `Drop` (that will need to be compiler magic). With `#[unpin]` I do not really see a way how it could be implemented.~~ -- [ ] Any new syntax? `(N)` (except the next point) -- [ ] Disambiguate member access could we do something like `.value`? `(Y)` -- [ ] Should we expose the `NoMetadataPtr` to the user? `(-)` -- [ ] What types should we also support? - [ ] should the warning when using `#[pin]` on an `Unpin` field be an error? `(Y)` # Future possibilities @@ -775,7 +608,7 @@ a public trait that can be implemented to allow this. `(Y)` ## Types that could benefit from projections -- [`Option`][option], it would be `#[inner_projecting(T)]`, so it allows projecting from `Option>` to `Option>` +- [`Option`][option], allowing projecting from `Option<&mut Struct>` to `Option<&mut Field>` - [`PhantomData`], it could allow macros to better access the type of a field. ## Arrays @@ -787,13 +620,28 @@ Even more generalized projections e.g. slices: At the moment exist, maybe there is room for generalization there as well. -## [`Rc`]`` and [`Arc`]`` projections +## `Arc` projection +[arc-projection]: #arc-projection -While out of scope for this RFC, projections for [`Rc`]`` and [`Arc`]`` -could be implemented by adding another field that points to the ref count. -This RFC is designed for low cost projections, modifying an atomic ref count is -too slow to let it happen without explicit opt-in by the programmer and as such -it would be better to implement it via a dedicated `map` function. +With the current design of `Arc` it is not possible to add field projection, +because the ref-count lives directly adjacent to the data. Instead the std-lib could +include a new type of `Arc` (or `ProjectedArc`) that allows +projection via a `map` function: +```rust +pub struct ProjectedArc { + backing: Arc, + ptr: NonNull, +} + +impl Arc { + pub fn project(&self, map: impl FnOnce(&T) -> &U) -> ProjectedArc { + ProjectedArc { + backing: self.clone(), + ptr: NonNull::from(map(&**self)), + } + } +} +``` [`Rc`]: https://doc.rust-lang.org/alloc/sync/struct.Rc.html [`Arc`]: https://doc.rust-lang.org/alloc/sync/struct.Arc.html From 845fd78c1849a88912620397be9b7130c56e858a Mon Sep 17 00:00:00 2001 From: y86-dev Date: Mon, 7 Nov 2022 19:49:13 +0100 Subject: [PATCH 18/28] Moved enum and union into future-possibilities and added unresolved questions --- text/3318-field-projection.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/text/3318-field-projection.md b/text/3318-field-projection.md index b3f57f1cf27..d42067a0ae7 100644 --- a/text/3318-field-projection.md +++ b/text/3318-field-projection.md @@ -599,9 +599,13 @@ You can find the direction I am currently biased towards at the end of each Ques `(N) = No`, `(-) = no bias`. Questions that have been crossed out are no longer relevant/have been answered with "No". -- [ ] Should `union`s also be supported? `(N)` -- [ ] How can `enum` and [`MaybeUninit`][maybeuninit]`` be made compatible? `(N)` - [ ] should the warning when using `#[pin]` on an `Unpin` field be an error? `(Y)` +- [ ] the current proposal requires explicit reborrowing, so `pinned.as_mut()->field`. Because it + would otherwise move `pinned`. Which might be used later. +- [ ] how should the `const N: usize` parameter of `Field` be calculated and exposed to the + user? There needs to be a way to refer to `Field` using only the base type `T` and the + name of the field. +- [ ] `HasFields` needs to be implemented automatically, how should this be done? # Future possibilities [future-possibilities]: #future-possibilities @@ -643,6 +647,15 @@ impl Arc { } ``` +## `enum` and `union` support + +When destructuring an enum, the discriminant needs to be read. The `MaybeUninit` wrapper type makes +this impossible, as it permits uninitialized data, making the read UB. There could be an unsafe way +of projecting the enum, with the assumption that the discriminant is initialized. + +Unions are probably simpler to implement, as they are much more similar to structs compared to +enums. But this is also left to a future RFC. + [`Rc`]: https://doc.rust-lang.org/alloc/sync/struct.Rc.html [`Arc`]: https://doc.rust-lang.org/alloc/sync/struct.Arc.html [`PhantomData`]: https://doc.rust-lang.org/core/marker/struct.PhantomData.html From 3d80738e845da2730911a71df855d457c35d1bd3 Mon Sep 17 00:00:00 2001 From: y86-dev Date: Mon, 28 Nov 2022 17:43:54 +0100 Subject: [PATCH 19/28] Extended motivation section --- text/3318-field-projection.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/text/3318-field-projection.md b/text/3318-field-projection.md index d42067a0ae7..56a6f3d3ada 100644 --- a/text/3318-field-projection.md +++ b/text/3318-field-projection.md @@ -152,6 +152,20 @@ where ``` It is the most important goal of this RFC to make field projection an ergonomic and safe operation. +While there exist macro solutions like `pin-project` and `pin-project-lite`, there are situations +where developers want to avoid the use of 3rd party libraries. In the case of the Rust-for-Linux +project, any proc macro solutions with `syn` is problematic. At the moment (as of 28.11.2022), +there are in total 10k lines of Rust code in the Linux kernel. Compare that with the over 50k lines +that `syn` has. It is very difficult to vendor all of this into the kernel any time soon. +`pin-project-lite` does not have this dependency problem. However, the macro itself is very +convoluted and difficult to understand. When this is proposed to the Linux maintainers, they have to +be able to understand the code, or at the very least comprehend the problem and the solution. +This is why a language level solution that additionally future proofs any needs for projections is +necessary. + +Additionally this RFC allows custom projections, these could be used in the Linux kernel to improve +interactions of RCU with other locking mechanisms. + # Guide-level explanation [guide-level-explanation]: #guide-level-explanation From 8f7da9e0187ac5ec26598c110ad3633984158888 Mon Sep 17 00:00:00 2001 From: y86-dev Date: Wed, 5 Apr 2023 14:48:13 +0200 Subject: [PATCH 20/28] Complete rewrite due to design meeting --- text/3318-field-projection.md | 681 +++++++--------------------------- 1 file changed, 127 insertions(+), 554 deletions(-) diff --git a/text/3318-field-projection.md b/text/3318-field-projection.md index 56a6f3d3ada..063413edc15 100644 --- a/text/3318-field-projection.md +++ b/text/3318-field-projection.md @@ -6,135 +6,18 @@ # Summary [summary]: #summary -The std-lib has wrapper types that impose some restrictions/additional features -on the types that are wrapped. For example: `MaybeUninit` allows `T` to be -partially initialized. These wrapper types also affect the fields of the types. -At the moment there is no easy access to these fields. -This RFC proposes to add field projection to certain wrapper types from the -std-lib: - -| from | to | -|-------------------------------------------------------|------------------------------------------------------| -|`&`[`MaybeUninit`][maybeuninit]`` |`&`[`MaybeUninit`][maybeuninit]`` | -|`&`[`Cell`][cell]`` |`&`[`Cell`][cell]`` | -|`&`[`UnsafeCell`][unsafecell]`` |`&`[`UnsafeCell`][unsafecell]`` | -|[`Pin`][pin]`<&Struct>` |[`Pin`][pin]`<&Field>` | -|[`Pin`][pin]`<&`[`MaybeUninit`][maybeuninit]`>`|[`Pin`][pin]`<&`[`MaybeUninit`][maybeuninit]`>`| - -Other pointers are also supported, for a list, see [here][supported-pointers]. - -Projection is facilitated by an operator and the syntax looks similar to normal field access. This -operator can be a new one (e.g. `~` or `->`) or it could be overloading `.`. This RFC will use `->` -as a placeholder for future bikeshedding. -```rust -struct MyStruct { - foo: Foo, - bar: usize, -} -struct Foo { - count: usize, -} -``` -when `mystruct` is of type `MyStruct`/`&mut MyStruct` field access works like this: - -| expression | type | -|-------------------------|-------------| -|`mystruct.foo` | `Foo` | -|`&mystruct.foo` | `&Foo` | -|`&mut mystruct.foo.count`|`&mut usize` | - -when `mystruct` is of type `&mut MaybeUninit` this proposal allows this: - -| expression | type | -|-------------------------|--------------------------| -|`mystruct->foo` | `MaybeUninit` | -|`mystruct->foo` | `&MaybeUninit` | -|`mystruct->foo->count`|`&mut MaybeUninit` | - - -[maybeuninit]: https://doc.rust-lang.org/core/mem/union.MaybeUninit.html -[cell]: https://doc.rust-lang.org/core/cell/struct.Cell.html -[unsafecell]: https://doc.rust-lang.org/core/cell/struct.UnsafeCell.html -[option]: https://doc.rust-lang.org/core/option/enum.Option.html -[pin]: https://doc.rust-lang.org/core/pin/struct.Pin.html -[ref]: https://doc.rust-lang.org/core/cell/struct.Ref.html -[refmut]: https://doc.rust-lang.org/core/cell/struct.RefMut.html +Rust often employs the use of wrapper types, for example `Pin

`, `NonNull`, `Cell`, `UnsafeCell`, `MaybeUninit` and more. These types provide additional properties for the wrapped type and often also logically affect their fields. For example, if a struct is uninitialized, its fields are also uninitialized. This RFC introduces architecture to make it possible to provide safe projections backed by the type system. # Motivation [motivation]: #motivation -There are situations that necessitate heavy usage of wrapper types instead of their underlying -pointers. In the Linux kernel for example many types need to be pinned, because they contain self -referential datastructures. This results in `Pin` being present almost everywhere. Thus pin -projections are required instead of normal accesses. - -Currently, there are mapping functions that provide this functionality. These -functions are not as ergonomic as a normal field access would be. Accessing the fields can also be a -totally safe operation, but the wrapper mapping functions need to be marked `unsafe`. This results -in poor API ergonomics: -```rust -struct Count { - inner: usize, - outer: usize, -} -fn init_count(mut count: Box>) -> Box { - let inner: &mut MaybeUninit = - unsafe { &mut *addr_of_mut!((*count.as_mut_ptr()).inner).cast::>() }; - inner.write(42); - unsafe { &mut *addr_of_mut!((*count.as_mut_ptr()).outer).cast::>() }.write(63); - unsafe { - // SAFETY: all fields have been initialized - count.assume_init() // #![feature(new_uninit)] - } -} -``` -Using the proposal from this RFC, the code simplifies to this: -```rust -struct Count { - inner: usize, - outer: usize, -} -fn init_count(mut count: Box>) -> Box { - let inner: &mut MaybeUninit = count->inner; - inner.write(42); - count->outer.write(63); - unsafe { - // SAFETY: all fields have been initialized - count.assume_init() // #![feature(new_uninit)] - } -} -``` -[`Pin`][pin]`

` has a similar story: -```rust -struct RaceFutures { - fut1: F1, - fut2: F2, -} -impl Future for RaceFutures -where - F1: Future, - F2: Future, -{ - type Output = F1::Output; - fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll { - match unsafe { self.map_unchecked_mut(|t| &mut t.fut1) }.poll(ctx) { - Poll::Pending => unsafe { self.map_unchecked_mut(|t| &mut t.fut2) }.poll(ctx), - rdy => rdy, - } - } -} -``` -It gets a lot simpler: +Some wrapper types provide projection functions, but these are not ergonomic. They also cannot automatically uphold type invariants of the projected struct. The prime example is `Pin`, the projection functions are `unsafe` and accessing fields is natural and often required. This leads to code littered with `unsafe` projections: ```rust struct RaceFutures { - // Pin is somewhat special, it needs some way to specify - // structurally pinned fields, because `Pin<&mut T>` might - // not affect the whole of `T`. - #[pin] fut1: F1, - #[pin] fut2: F2, } + impl Future for RaceFutures where F1: Future, @@ -142,418 +25,169 @@ where { type Output = F1::Output; - fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll { - match self->fut1.poll(ctx) { - Poll::Pending => self->fut2.poll(ctx), + fn poll(mut self: Pin<&mut Self>, ctx: &mut Context) -> Poll { + match unsafe { self.as_mut().map_unchecked_mut(|t| &mut t.fut1) }.poll(ctx) { + Poll::Pending => { + unsafe { self.map_unchecked_mut(|t| &mut t.fut2) }.poll(ctx) + } rdy => rdy, } } } ``` -It is the most important goal of this RFC to make field projection an ergonomic and safe operation. - -While there exist macro solutions like `pin-project` and `pin-project-lite`, there are situations -where developers want to avoid the use of 3rd party libraries. In the case of the Rust-for-Linux -project, any proc macro solutions with `syn` is problematic. At the moment (as of 28.11.2022), -there are in total 10k lines of Rust code in the Linux kernel. Compare that with the over 50k lines -that `syn` has. It is very difficult to vendor all of this into the kernel any time soon. -`pin-project-lite` does not have this dependency problem. However, the macro itself is very -convoluted and difficult to understand. When this is proposed to the Linux maintainers, they have to -be able to understand the code, or at the very least comprehend the problem and the solution. -This is why a language level solution that additionally future proofs any needs for projections is -necessary. - -Additionally this RFC allows custom projections, these could be used in the Linux kernel to improve -interactions of RCU with other locking mechanisms. +Since the supplied closures are only allowed to do field projections, it would be natural to add `SAFETY` comments, but that gets even more tedious. -# Guide-level explanation -[guide-level-explanation]: #guide-level-explanation +Other types would also greatly benefit from projections, for example raw pointers, since projection could be based on `wrapping_add` and they would not dereference anything. It would reduce syntactic clutter of `(*ptr).field`. -## [`MaybeUninit`][maybeuninit]`` +Cell types like `Cell`, `UnsafeCell` would similarly enjoy additional ergonomics, since they also propagate their properties to the fields of structs. -When working with certain wrapper types in rust, one often want to access fields -of the wrapped types. When interfacing with C one often has to deal with -uninitialized data. In Rust, uninitialized data is represented by -[`MaybeUninit`][maybeuninit]``. In the following example we demonstrate -how one can initialize fields using [`MaybeUninit`][maybeuninit]``. -```rust -#[repr(C)] -pub struct MachineData { - incident_count: u32, - device_id: usize, - device_specific: *const core::ffi::c_void, -} -extern "C" { - // provided by the C code - /// Initializes the `device_specific` pointer based on the value of `device_id`. - /// Returns -1 on error (unknown id) and 0 on success. - fn lookup_device_ptr(data: *mut MachineData) -> i32; -} +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation -pub struct UnknownId; - -impl MachineData { - pub fn new(id: usize) -> Result { - let mut this = MaybeUninit::::uninit(); - this->device_id.write(id); - this->incident_count.write(0); - // SAFETY: ffi-call, `device_id` has been initialized - if unsafe { lookup_device_ptr(this.as_mut_ptr()) } != 0 { - Err(UnknownId) - } else { - // SAFETY: all fields have been initialized - Ok(unsafe { this.assume_init() }) - } - } -} -``` -So to access a field of [`MaybeUninit`][maybeuninit]`` we can use similar syntax to -accessing a field of `MachineData`. The difference is that the type of the expression -`this->device_id` is now [`MaybeUninit`][maybeuninit]``. +This section will be created when a solution has been agreed upon. -These *field projections* are also available on other types. +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation -## [`Pin`][pin]`

` projections +## Core design -Our second example is going to focus on [`Pin`][pin]`

`. This type is a little -special, as it allows unwrapping while projecting, but only for specific fields. -This information is expressed via the `#[pin]` attribute on each structurally pinned field. -Untagged fields are *unwrapped* when projected. So the projection is `Pin<&mut Struct> -> &mut -Field`. +For every field of every struct, the compiler creates unnameable unit structs that represent that field. These types will have a meaningful name in error messages (e.g. `Struct::field`). These types implement the `Field` trait holding information about the type of the field and its offset within the struct: ```rust -use core::pin::pin; -struct RaceFutures { - #[pin] - fut1: F1, - #[pin] - fut2: F2, - // this will be used to fairly poll the futures - first: bool, -} -impl Future for RaceFutures -where - F1: Future, - F2: Future, -{ - type Output = F1::Output; - - fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll { - // we can access self.first mutably, because it is not `#[pin]` - *self->first = !*self->first; - if *self->first { - // `self->fut1` has the type `Pin<&mut F1>` because `fut1` is a pinned field. - // if it was not pinned, the type would be `&mut F1`. - match self->fut1.poll(ctx) { - Poll::Pending => self->fut2.poll(ctx), - rdy => rdy, - } - } else { - match self->fut2.poll(ctx) { - Poll::Pending => self->fut1.poll(ctx), - rdy => rdy, - } - } - } +// in core::marker: +/// A type representing a field on a struct. +pub trait Field { + /// The type of the struct containing this field. + type Base; + /// The type of this field. + type Type; + /// The offset of this field from the beginning of the `Base` struct in bytes. + const OFFSET: usize; } ``` - -## Defining your own wrapper type - -If you want to add field projection to a wrapper type, you will need to implement the `Project` -trait for the type that you want to project: +This trait cannot be implemented manually and users are allowed to rely on the associated types/constants to be correct. For example the following code is allowed: ```rust -#[repr(transparent)] -pub struct Wrapper(T); - -impl<'a, T> Project<'a> for &'a mut Wrapper -where - T: HasFields // this trait ensures that T actually has fields to project. -{ - // This type is the type that will be projected. - type Inner = T; - // This is the type resulting from a projection to a field of type U. - type Output = &'a mut Wrapper; - // This is the type resulting from unwrapping a field of type U. - type Unwrap = &'a mut U; - - unsafe fn project_true(self, field: Field) -> Self::Output { - // SAFETY: because Wrapper is repr(transparent), we can do the cast - // and because of field's invariants this results in the correct field pointer. - unsafe { - // we can project `*mut T` to `*mut U` using `project` (this is provided by the - // `Project` trait). - &mut *(&mut self.0 as *mut T).project(field).cast::>() - } - } - - unsafe fn unwrap_true(self, field: Field) -> Self::Unwrap { - // SAFETY: because Wrapper is repr(transparent), we can do the cast - // and because of field's invariants this results in the correct field pointer. - unsafe { - // we can project `*mut T` to `*mut U` using `project` (this is provided by the - // `Project` trait). - &mut *(&mut self.0 as *mut T).project(field) - } - } +fn project(base: &F::Base) -> &F::Type { + let ptr: *const Base = base; + let ptr: *const u8 = base.cast::(); + // SAFETY: `ptr` is derived from a reference and the `Field` trait is guaranteed to contain + // correct values. So `F::OFFSET` is still within the `F::Base` type. + let ptr: *const u8 = unsafe { ptr.add(F::OFFSET) }; + let ptr: *const F::Type = ptr.cast::(); + // SAFETY: The `Field` trait guarantees that at `F::OFFSET` we find a field of type `F::Type`. + unsafe { &*ptr } } ``` -Now anyone can project `&mut Wrapper -> &mut Wrapper` via `struct->field` if the -field is visible. Additionally the field needs to be projectable. This is expressed by the -`Projectable` trait. So the projection is only allowed, if `Field: Projectable<&mut Wrapper>`. -`N` is a compiler generated identifier of the specific field. This implementation needs to be -provided by the author of `Struct`. -# Reference-level explanation -[reference-level-explanation]: #reference-level-explanation +Importantly, the `Field` trait should only be implemented on non-`packed` structs, since otherwise the above code would not be sound. -The following types will be added to `core`: +Users will be able to name this type by invoking the compiler built-in macro `field_of!` that takes a struct and an identifier/number for the accessed field: ```rust -pub unsafe trait Project<'a>: 'a + Sized { - type Inner: 'a + HasFields; - type Projected: 'a - where - Self: 'a; - type Unwrapped: 'a - where - Self: 'a; - - fn project( - self, - field: Field, - ) -> < as Projectable<'a, Self>>::ProjKind as ProjSelector<'a, Self>>::Output - where - Field: Projectable<'a, Self>, - as Projectable<'a, Self>>::ProjKind: ProjSelector<'a, Self>, - { - unsafe { - as Projectable<'a, Self>>::ProjKind::select_proj(self, field) - } - } - - unsafe fn project_field( - self, - field: Field, - ) -> Self::Projected - where - Field: Projectable<'a, Self>; - - unsafe fn unwrap_field( - self, - field: Field, - ) -> Self::Unwrapped - where - Field: Projectable<'a, Self>; +// in core: +macro_rules! field_of { + ($struct:ty, $field:tt) => { /* compiler built-in */ } } +``` +## Improving ergonomics 1: Closures -pub struct Field { - offset: usize, - phantom: PhantomData (T, U)>, +For improving the ergonomics of getting a field of a specific type, we could leverage specially marked closures: +```rust +// in core::marker: +pub trait FieldClosure: Fn { + type Field: Field; } - -impl Field { - pub const unsafe fn new(offset: usize) -> Self { - Self { - offset, - phantom: PhantomData, - } - } - - pub fn offset(&self) -> usize { - self.offset - } +``` +This trait is also only implementable by the compiler. It is implemented for closures that +- do not capture variables +- only do a single field access on the singular parameter they have + +Positive example: `|foo| foo.bar` +Negative examples: +- `|_| foo.bar`, captures `foo` +- `|foo| foo.bar.baz`, does two field accesses +- `|foo| foo.bar()`, calls a function +- `|foo| &mut foo.bar`, creates a reference to the field +- `|foo, bar| bar.baz`, takes two parameters + +With this trait one could write a function like this: +```rust +pub unsafe fn map(pin: Pin<&mut T>, f: F) -> Pin<&mut ::Type> +where + F: FieldClosure, +{ + /* do the offsetting */ } +``` -pub struct Projected; -pub struct Unwrapped; +To make this function safe, we require the feature discussed in the next section. -mod sealed { - pub unsafe trait IsField {} - unsafe impl IsField for super::Field {} +## Limited negative reasoning - pub unsafe trait IsProjKind {} - unsafe impl IsProjKind for super::Projected {} - unsafe impl IsProjKind for super::Unwrapped {} -} +There is the need to make the output type of the `map` function above depend on a property of the field. In the case of `Pin`, this is whether the field is structurally pinned or not. If it is, then the return type should be as declared above, if it is not, then it should be `&mut ::Type` instead. -pub unsafe trait ProjSelector<'a, P: Project<'a>>: sealed::IsProjKind { - type Output: 'a - where - Self: 'a; - unsafe fn select_proj( - proj: P, - field: Field, - ) -> Self::Output - where - Field: Projectable<'a, P>; -} +A way this could be expressed is by allowing some negative reasoning. Here is the solution discussed on the example of `Pin`: +```rust +// First we create a marker trait to differ structurally pinned fields: +pub trait StructurallyPinnedField: Field {} +// This trait needs to then be implemented for every field that should be structurally pinned. +// Since this is user-decideable at the struct definition, this could be done with a proc-macro akin to `pin-project`. -unsafe impl<'a, P: Project<'a>> ProjSelector<'a, P> for Projected { - type Output = P::Projected; - unsafe fn select_proj( - proj: P, - field: Field, - ) -> Self::Output +impl Pin<&mut T> { + pub fn map>(self, f: F) -> Pin<&mut ::Type> where - Field: Projectable<'a, P>, - { - P::project_field(proj, field) - } -} + F::Field: StructurallyPinnedField, + { /* do the offsetting */ } -unsafe impl<'a, P: Project<'a>> ProjSelector<'a, P> for Unwrapped { - type Output = P::Unwrapped; - unsafe fn select_proj( - proj: P, - field: Field, - ) -> Self::Output + pub fn map>(self, f: F) -> &mut ::Type where - Field: Projectable<'a, P>, - { - P::unwrap_field(proj, field) - } -} - -pub trait Projectable<'a, P: Project<'a>>: sealed::IsField { - type ProjKind: sealed::IsProjKind; + F::Field: !StructurallyPinnedField, + { /* do the offsetting */ } } ``` -This design is very flexible for authors writing wrapper types. There is almost no restriction on -what can be projected. And this design includes direct support for unwrapping. - -Here is the list of types from `core` that will have a projection implementation: - -- `&mut T`, `&T` -- `*mut T`, `*const T` -- [`&mut MaybeUninit`][maybeuninit] -- [`&Cell`][cell], [`&UnsafeCell`][unsafecell] -- [`Pin<&mut T>`][pin], `Pin<&mut MaybeUninit>` - -## Pin projections -[pin-projections-section]: #pin-projections - -Because [`Pin

`][pin] permits unwrapping when the user specifies so. There are multiple ways to -implement this: - -- nothing, users can add their own implementations. -- introduce an attribute `#[pin]` and/or `#[unpin]` on fields that automatically add the respective - implementation. -- a derive/attribute/function-like macro that adds the implementations. +The compiler would need to be able to prove that these two functions do not overlap for this to work. -This RFC is going to use `#[pin]` in the next section to refer to the method of marking a field -pin-projected. +A variation of this feature is `xor` traits, where a type is only ever allowed to implement one from the given set. It could achieve the same thing, while being more flexible when one wants to define more than two different user-specified projections. -### `PinnedDrop` +## Alternative to negative reasoning -An additional challenge is that if a `!Unpin` field is marked `#[pin]`, then -one cannot implement the normal `Drop` trait on the struct, as it would give access to -`&mut self` even if `self` is pinned. Before this did not pose a problem, because -users would have to use `unsafe` to project `!Unpin` fields. But as this -proposal makes this possible, we have to account for this. - -The solution is similar to how [pin-project] solves this issue: Users are not -allowed to implement `Drop` manually, but instead can implement `PinnedDrop`: -```rust -pub trait PinnedDrop { - fn drop(self: Pin<&mut Self>); -} -``` -similar to `Drop::drop`, `PinnedDrop::drop` would not be callable by normal code. -The compiler would emit the following `Drop` stub for types that had `#[pin]`ned -fields and a user specified `PinnedDrop` impl: -```rust -impl Drop for $ty { - fn drop(&mut self) { - // SAFETY: because `self` is being dropped, there exists no other reference - // to it. Thus it will never move, if this function never moves it. - let this = unsafe { ::core::pin::Pin::new_unchecked(self) }; - ::drop(this) - } -} -``` - -An error is emitted if a `Drop` impl is found when at least one field is marked `#[pin]`: +One alternative is to also create an extension trait of `Field`, but then rely on the proc-macro to specify the correct projection-output type in an associated type: ```rust -struct MyStruct { - buf: [u8; 64], - ptr: *const u8, - #[pin] - _pin: PhantomPinned, +// We have to mark it `unsafe`, because the `ProjOutput` type could be wrongly specified. +pub unsafe trait PinProjectableField: Field { + type ProjOutput<'a>; // this is either `&'a mut Self::Type` or `Pin<&'a mut Self::Type>`. } -impl Drop for MyStruct { -// ^^^^ error: cannot implement `Drop` for `MyStruct` because it has pinned fields. -// help: implement `PinnedDrop` instead - fn drop(&mut self) { - println!("Dropping MyStruct"); - } +impl<'a, T> Pin<&'a, mut T> { + pub fn map>(self, f: F) -> ::ProjOutput<'a> + where + F::Field: PinProjectableField, + { /* do the offsetting */ } } ``` +This approach is used by [the field projection example from Gary](https://github.com/nbdd0121/field-projection/). -`PinnedDrop` would also reside in `core::ops` and should be added to the prelude in the next -edition. +A big issue that this approach has is that the `map` function cannot have behavior depending on the projection output. This results in practice, that `mem::transmute_copy` has to be used, since the compiler cannot prove that `ProjOutput` always has the same size. # Drawbacks [drawbacks]: #drawbacks -- Users currently relying on crates that facilitate field projections (see -[prior art][prior-art]) will have to refactor their code. -- Increased compiler complexity: - - longer compile times - - potential worse type inference - - `Pin` projection support might be confusing to new users - +Adds considerable complexity. # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives -This RFC consciously chose the presented design, because it addresses the -following core issues: -- ergonomic field projection for a wide variety of types with user accessible - ways of implementing it for their own types. - - -## Create a field projection operator - -In [Gankra's blog post][faultlore] about overhauling raw pointers she talks about using `~` as field -projection for raw pointers. - -We could implement field projection for raw pointers with the current approach, but that would -result in `x.y`. If we instead adopt Gankra's idea for field projection in general, it would also -more clearly convey the intent. - -### Advantages: - -- we can imbue `~` with new meaning, users will not assume anything about what the operator does - from other languages -- it clearly differentiates it from normal field access (and will not have auto-deref like `.`) - -### Disadvantages: +## Why specify projections via proc-macros? -- `.` is less confusing to beginners compared to `~`. Other languages use it primarily as a unary - binary negation operator -- migrating from `pin-project` is going to be a *lot* tougher. users will have to change every - access via `.` to `~` compared to just having to remove `.project`. - - -## What other designs have been considered and what is the rationale for not choosing them? - -This proposal was initially only designed to enable projecting -[`Pin`][pin]`<&mut T>`, because that would remove the need for `unsafe` when -pin projecting. - -It seems beneficial to also provide this functionality for a wider range of types. - -### Using solely `#[pin]`/`#[unpin]` for specifying structurally pinned fields +In the design meeting the following alternative was brought up: +```rust +pub struct NotPin(pub T); -Because it makes existing code unsound this option has not been chosen. +impl Unpin for NotPin {} +``` +Instead of marking fields with `#[pin]`, they should wrap not structurally pinned fields with the `NotPin` struct. The `map` function of `Pin` would then use `!Unpin`/`Unpin` instead of the `StructurallyPinnedField` field used here. -## What is the impact of not doing this? +The problem with this solution is that now users always get reminded that the field is not structurally pinned. They have to wrap a value when they want to assign it and they have to unwrap it when they want to read it. This property is also not something for the type system, rather it is a property of that specific field of the struct. -Users of these wrapper types need to rely on crates listed in [prior art][prior-art] -to provide sensible projections. Otherwise they can use the mapping functions -provided by some of the wrapper types. These are however, rather unergonomic -and wrappers like [`Pin`][pin]`

` require `unsafe`. # Prior art [prior-art]: #prior-art @@ -562,41 +196,20 @@ and wrappers like [`Pin`][pin]`

` require `unsafe`. There are some crates that enable field projections via (proc-)macros: -- [pin-project] provides pin projections via a proc macro on the type specifying -the structurally pinned fields. At the projection-site the user calls a projection -function `.project()` and then receives a type with each field replaced with -the respective projected field. -- [field-project] provides pin/uninit projection via a macro at the projection-site: -the user writes `proj!($var.$field)` to project to `$field`. It works by -internally using `unsafe` and thus cannot pin-project `!Unpin` fields, because -that would be unsound due to the `Drop` impl a user could write. -- [cell-project] provides cell projection via a macro at the projection-site: -the user writes `cell_project!($ty, $val.$field)` where `$ty` is the type of `$val`. -Internally, it uses unsafe to facilitate the projection. -- [pin-projections] provides pin projections, it differs from [pin-project] by -providing explicit projection functions for each field. It also can generate -other types of getters for fields. [pin-project] seems like a more mature solution. -- [project-uninit] provides uninit projections via macros at the projection-site -uses `unsafe` internally. - -All of these crates have in common that their users have to use macros -when they want to perform a field projection. +- [pin-project] provides pin projections via a proc macro on the type specifying the structurally pinned fields. At the projection-site the user calls a projection function `.project()` and then receives a type with each field replaced with the respective projected field. +- [field-project] provides pin/uninit projection via a macro at the projection-site: the user writes `proj!($var.$field)` to project to `$field`. It works by internally using `unsafe` and thus cannot pin-project `!Unpin` fields, because that would be unsound due to the `Drop` impl a user could write. +- [cell-project] provides cell projection via a macro at the projection-site: the user writes `cell_project!($ty, $val.$field)` where `$ty` is the type of `$val`. Internally, it uses unsafe to facilitate the projection. +- [pin-projections] provides pin projections, it differs from [pin-project] by providing explicit projection functions for each field. It also can generate other types of getters for fields. [pin-project] seems like a more mature solution. +- [project-uninit] provides uninit projections via macros at the projection-site uses `unsafe` internally. +- [field-projection] is an experimental crate that implements general field projections via a proc-macro that hashes the name of the field to create unique types for each field that can then implement traits to make different output types for projections. ## Other languages -Other languages generally do not have this feature in the same extend. C++ has -`shared_ptr` which allows the creation of another `shared_ptr` pointing at a field of -a `shared_ptr`'s pointee. This is possible, because `shared_ptr` is made up of -two pointers, one pointing to the data and another pointing at the ref count. -While this is not possible to add to `Arc` without introducing a new field, it -could be possible to add another `Arc` pointer that allowed field projections. -See [the future possibilities section][arc-projection] for more. +Other languages generally do not have this feature in the same extend. C++ has `shared_ptr` which allows the creation of another `shared_ptr` pointing at a field of a `shared_ptr`'s pointee. This is possible, because `shared_ptr` is made up of two pointers, one pointing to the data and another pointing at the ref count. While this is not possible to add to `Arc` without introducing a new field, it could be possible to add another `Arc` pointer that allowed field projections. See [the future possibilities section][arc-projection] for more. ## RFCs - - [`ptr-to-field`](https://github.com/rust-lang/rfcs/pull/2708) - ## Further discussion - https://internals.rust-lang.org/t/cell-references-and-struct-layout/11564 @@ -605,72 +218,32 @@ See [the future possibilities section][arc-projection] for more. [cell-project]: https://crates.io/crates/cell-project [pin-projections]: https://crates.io/crates/pin-projections [project-uninit]: https://crates.io/crates/project-uninit +[field-projection]: https://crates.io/crates/field-projection # Unresolved questions [unresolved-questions]: #unresolved-questions -You can find the direction I am currently biased towards at the end of each Question. `(Y) = Yes`, -`(N) = No`, `(-) = no bias`. Questions that have been crossed out are no longer relevant/have been -answered with "No". - -- [ ] should the warning when using `#[pin]` on an `Unpin` field be an error? `(Y)` -- [ ] the current proposal requires explicit reborrowing, so `pinned.as_mut()->field`. Because it - would otherwise move `pinned`. Which might be used later. -- [ ] how should the `const N: usize` parameter of `Field` be calculated and exposed to the - user? There needs to be a way to refer to `Field` using only the base type `T` and the - name of the field. -- [ ] `HasFields` needs to be implemented automatically, how should this be done? +The whole design, please look at the PR to see the currently open questions. # Future possibilities [future-possibilities]: #future-possibilities -## Types that could benefit from projections +## Operator syntax -- [`Option`][option], allowing projecting from `Option<&mut Struct>` to `Option<&mut Field>` -- [`PhantomData`], it could allow macros to better access the type of a field. +Introduce a `Project` trait and a binary operator that is syntactic sugar for `Project::project($left, |f| f.$right)`. -## Arrays +## Project multiple field at once -Even more generalized projections e.g. slices: At the moment +Introduce a `FieldChainClosure` trait that is implemented for closures that contain only a field access chain `|foo| foo.bar.baz`. The chain should not contain `Box`es or other types with `deref`s, since then we could be leaving the allocation of the base struct. -- [`as_array_of_cells`](https://doc.rust-lang.org/core/cell/struct.Cell.html#method.as_array_of_cells) -- [`as_slice_of_cells`](https://doc.rust-lang.org/core/cell/struct.Cell.html#method.as_slice_of_cells) +## Support misaligned fields and `packed` structs -exist, maybe there is room for generalization there as well. - -## `Arc` projection -[arc-projection]: #arc-projection - -With the current design of `Arc` it is not possible to add field projection, -because the ref-count lives directly adjacent to the data. Instead the std-lib could -include a new type of `Arc` (or `ProjectedArc`) that allows -projection via a `map` function: -```rust -pub struct ProjectedArc { - backing: Arc, - ptr: NonNull, -} - -impl Arc { - pub fn project(&self, map: impl FnOnce(&T) -> &U) -> ProjectedArc { - ProjectedArc { - backing: self.clone(), - ptr: NonNull::from(map(&**self)), - } - } -} -``` +Create the `MaybeUnalignedField` trait that also has a constant `WELL_ALIGNED: bool`. This trait is also automatically implemented by the compiler even for packed structs. ## `enum` and `union` support -When destructuring an enum, the discriminant needs to be read. The `MaybeUninit` wrapper type makes -this impossible, as it permits uninitialized data, making the read UB. There could be an unsafe way -of projecting the enum, with the assumption that the discriminant is initialized. +Both enums and unions cannot be treated like structs, since some variants might not be currently valid. This makes these fundamentally incompatible with the code that this RFC tries to enable. They could be handled using similar traits, but these would not guarantee the same things. For example, union fields are always allowed to be uninitialized. -Unions are probably simpler to implement, as they are much more similar to structs compared to -enums. But this is also left to a future RFC. +## Field marco attributes -[`Rc`]: https://doc.rust-lang.org/alloc/sync/struct.Rc.html -[`Arc`]: https://doc.rust-lang.org/alloc/sync/struct.Arc.html -[`PhantomData`]: https://doc.rust-lang.org/core/marker/struct.PhantomData.html -[faultlore]: https://faultlore.com/blah/fix-rust-pointers/ +To make things easier for implementing custom projections, we could create a new proc-macro kind that is placed on fields. From 2c44de6138dde4552ce4180242e1f87a41e2af1d Mon Sep 17 00:00:00 2001 From: y86-dev Date: Wed, 5 Apr 2023 17:32:03 +0200 Subject: [PATCH 21/28] Add negative reasoning limitations --- text/3318-field-projection.md | 42 ++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/text/3318-field-projection.md b/text/3318-field-projection.md index 063413edc15..2580f1c551f 100644 --- a/text/3318-field-projection.md +++ b/text/3318-field-projection.md @@ -88,6 +88,20 @@ macro_rules! field_of { ($struct:ty, $field:tt) => { /* compiler built-in */ } } ``` +`Project` trait, this trait permits changing the output type of projections based on properties of the projected field: +```rust +/// Used for projection operations like `expr->field`. +/// +/// `F` is the projected Field of the inner type. +pub trait Project { + /// The output of the projection. + type Output; + + /// Projects this wrapper type to the given field. + fn project(self, f: F) -> Self::Output; +} +``` + ## Improving ergonomics 1: Closures For improving the ergonomics of getting a field of a specific type, we could leverage specially marked closures: @@ -128,23 +142,35 @@ There is the need to make the output type of the `map` function above depend on A way this could be expressed is by allowing some negative reasoning. Here is the solution discussed on the example of `Pin`: ```rust // First we create a marker trait to differ structurally pinned fields: +#[with_negative_reasoning] pub trait StructurallyPinnedField: Field {} // This trait needs to then be implemented for every field that should be structurally pinned. // Since this is user-decideable at the struct definition, this could be done with a proc-macro akin to `pin-project`. -impl Pin<&mut T> { - pub fn map>(self, f: F) -> Pin<&mut ::Type> - where - F::Field: StructurallyPinnedField, +impl<'a, T, F: FieldClosure> Project for Pin<&'a mut T> +where + F::Field: StructurallyPinnedField, +{ + type Output = Pin<&'a mut ::Type>; + pub fn map(self, f: impl) -> Self::Output { /* do the offsetting */ } +} - pub fn map>(self, f: F) -> &mut ::Type - where - F::Field: !StructurallyPinnedField, +impl<'a, T, F: FieldClosure> Project for Pin<&'a mut T> +where + F::Field: !StructurallyPinnedField, +{ + type Output = &'a mut ::Type; + pub fn map(self, f: F) -> Self::Output { /* do the offsetting */ } } ``` -The compiler would need to be able to prove that these two functions do not overlap for this to work. +The `#[with_negative_reasoning]` attribute on a `Trait` result in the following: +- `!Trait` can be used in where clauses. +- `T: !Trait` means that there exists an explicit `impl !Trait for T` somewhere. +- Typechk knows that `Trait` and `!Trait` are mutually exclusive and thus can identify non-overlapping impl blocks. +- When `Trait` or `!Trait` are implemented, they have to be implemented for the entire type. This behavior is similar to `Drop`. So you are not able to implement it only for `Foo<'static>`. + A variation of this feature is `xor` traits, where a type is only ever allowed to implement one from the given set. It could achieve the same thing, while being more flexible when one wants to define more than two different user-specified projections. From ec823df12166b58863174bfedef22336c55c4277 Mon Sep 17 00:00:00 2001 From: y86-dev Date: Mon, 17 Apr 2023 22:52:09 +0200 Subject: [PATCH 22/28] Minimize proposed features Change from field projection to field information, this RFC is only about making field information available, since projections can be done via proc-macros. --- text/3318-field-projection.md | 284 ++++++++++++++++++---------------- 1 file changed, 150 insertions(+), 134 deletions(-) diff --git a/text/3318-field-projection.md b/text/3318-field-projection.md index 2580f1c551f..105d2d766a5 100644 --- a/text/3318-field-projection.md +++ b/text/3318-field-projection.md @@ -6,12 +6,15 @@ # Summary [summary]: #summary -Rust often employs the use of wrapper types, for example `Pin

`, `NonNull`, `Cell`, `UnsafeCell`, `MaybeUninit` and more. These types provide additional properties for the wrapped type and often also logically affect their fields. For example, if a struct is uninitialized, its fields are also uninitialized. This RFC introduces architecture to make it possible to provide safe projections backed by the type system. +Introduce ways to refer to fields of structs via the type system. # Motivation [motivation]: #motivation -Some wrapper types provide projection functions, but these are not ergonomic. They also cannot automatically uphold type invariants of the projected struct. The prime example is `Pin`, the projection functions are `unsafe` and accessing fields is natural and often required. This leads to code littered with `unsafe` projections: +Accessing field information is at the moment only possible for macros. Allowing the type system to also access some information about fields enables writing code that generalizes over fields. +One important application is field projection. Rust often employs the use of wrapper types, for example `Pin

`, `NonNull`, `Cell`, `UnsafeCell`, `MaybeUninit` and more. These types provide additional properties for the wrapped type and often also logically affect their fields. For example, if a struct is uninitialized, its fields are also uninitialized. Giving the type system access to field information allows creating safe projection functions. + +Current projection functions cannot be safe, since they take a projection closure that might execute arbitrary code. They also cannot automatically uphold type invariants of the projected struct. A prime example is `Pin`, the projection functions are `unsafe` and accessing fields is natural and often required. This leads to code littered with `unsafe` projections: ```rust struct RaceFutures { fut1: F1, @@ -37,28 +40,103 @@ where ``` Since the supplied closures are only allowed to do field projections, it would be natural to add `SAFETY` comments, but that gets even more tedious. -Other types would also greatly benefit from projections, for example raw pointers, since projection could be based on `wrapping_add` and they would not dereference anything. It would reduce syntactic clutter of `(*ptr).field`. - -Cell types like `Cell`, `UnsafeCell` would similarly enjoy additional ergonomics, since they also propagate their properties to the fields of structs. - # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -This section will be created when a solution has been agreed upon. +## Field Information + +When defining a struct, the compiler automatically creates types for each field. This allows referencing fields via the type system. For example when we define the following struct: +```rust +struct Problem { + info: String, + count: usize, +} +``` +Then the compiler creates two types, one for `info` and one for `count`. We cannot name these types normally, instead we use the `field_of!` macro: +```rust +type ProblemInfo<'a> = field_of!(Problem, info); +``` +This field type also implements the `Field` trait that cannot be manually implemented. This trait provides some information about the field: +- which struct it belongs to, +- its own type, +- the offset at which the field can be found inside of the struct. + +Since the trait cannot be implemented manually, you can be sure that a type implementing it actually refers to a field: +```rust +fn get_field>(problem: &Problem) -> &T::Type { + let ptr: *const Problem = problem; + // SAFETY: `F` implements the `Field` trait and thus we find `F::Type` at `F::OFFSET` inside + // of `ptr` that was derived from a reference. + unsafe { &*ptr.cast::().add(F::OFFSET).cast::() } +} +``` +There are a lot more powerful things that one can do using this type. For example field projections can be expressed safely. If we are often working with memory that has to be accessed volatile, then we might write the following wrapper type: +```rust +/// A pointer to memory that enforces volatile access. +pub struct VolatileMem { + ptr: NonNull, +} + +impl VolatileMem { + pub fn get(&self) -> T { + // SAFETY: `ptr` is always valid for volatile reads. + unsafe { ptr::read_volatile(self.ptr.as_ptr()) } + } + + pub fn put(&mut self, val: T) { + // SAFETY: `ptr` is always valid for volatile writes. + unsafe { ptr::write_volatile(self.ptr.as_mut_ptr()) } + } +} +``` +Now consider the following struct that we would like to put into our `VolatileMem`: +```rust +#[repr(C)] +pub struct Config { + mode: u8, + reserved: [u8; 128], +} +``` +If we want to write a new config, then we always have to write the whole struct, including the `reserved` field that is comparatively big. We can avoid this by providing a field projection: +```rust +impl VolatileMem { + pub fn map>(self) -> VolatileMem { + Self { + // SAFETY: `F` implements the `Field` trait and thus we find `F::Type` at `F::OFFSET` + // inside of `ptr` that is always valid. + ptr: unsafe { + NonNull::new_unchecked( + self.ptr.as_ptr().cast::().add(F::OFFSET).cast::(), + ) + }, + } + } +} +``` +Now in the scenario from above we can do: +```rust +let mut config: VolatileMem = ...; +config.put(Config::default()); +let mut mode: VolatileMem = config.map::(); +mode.put(1); +``` +And we will not have to always overwrite `reserved` with the same data. + # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -## Core design +For every field of every non-packed struct, the compiler creates a unique, unnameable type that represent that field. These generated types will: +- have meaningful names in error messages (e.g. `Struct::field`), +- implement the `Field` with accurate associated types and constants. -For every field of every struct, the compiler creates unnameable unit structs that represent that field. These types will have a meaningful name in error messages (e.g. `Struct::field`). These types implement the `Field` trait holding information about the type of the field and its offset within the struct: +The `Field` trait will reside in `core::marker` and is: ```rust -// in core::marker: /// A type representing a field on a struct. pub trait Field { /// The type of the struct containing this field. - type Base; + type Struct; /// The type of this field. type Type; /// The offset of this field from the beginning of the `Base` struct in bytes. @@ -81,138 +159,39 @@ fn project(base: &F::Base) -> &F::Type { Importantly, the `Field` trait should only be implemented on non-`packed` structs, since otherwise the above code would not be sound. -Users will be able to name this type by invoking the compiler built-in macro `field_of!` that takes a struct and an identifier/number for the accessed field: +Users will be able to name this type by invoking the compiler built-in macro `field_of!` residing in `core`. This macro takes a struct type and an identifier/number for the accessed field: ```rust -// in core: macro_rules! field_of { ($struct:ty, $field:tt) => { /* compiler built-in */ } } ``` -`Project` trait, this trait permits changing the output type of projections based on properties of the projected field: -```rust -/// Used for projection operations like `expr->field`. -/// -/// `F` is the projected Field of the inner type. -pub trait Project { - /// The output of the projection. - type Output; - - /// Projects this wrapper type to the given field. - fn project(self, f: F) -> Self::Output; -} -``` - -## Improving ergonomics 1: Closures - -For improving the ergonomics of getting a field of a specific type, we could leverage specially marked closures: -```rust -// in core::marker: -pub trait FieldClosure: Fn { - type Field: Field; -} -``` -This trait is also only implementable by the compiler. It is implemented for closures that -- do not capture variables -- only do a single field access on the singular parameter they have - -Positive example: `|foo| foo.bar` -Negative examples: -- `|_| foo.bar`, captures `foo` -- `|foo| foo.bar.baz`, does two field accesses -- `|foo| foo.bar()`, calls a function -- `|foo| &mut foo.bar`, creates a reference to the field -- `|foo, bar| bar.baz`, takes two parameters - -With this trait one could write a function like this: -```rust -pub unsafe fn map(pin: Pin<&mut T>, f: F) -> Pin<&mut ::Type> -where - F: FieldClosure, -{ - /* do the offsetting */ -} -``` - -To make this function safe, we require the feature discussed in the next section. - -## Limited negative reasoning - -There is the need to make the output type of the `map` function above depend on a property of the field. In the case of `Pin`, this is whether the field is structurally pinned or not. If it is, then the return type should be as declared above, if it is not, then it should be `&mut ::Type` instead. - -A way this could be expressed is by allowing some negative reasoning. Here is the solution discussed on the example of `Pin`: -```rust -// First we create a marker trait to differ structurally pinned fields: -#[with_negative_reasoning] -pub trait StructurallyPinnedField: Field {} -// This trait needs to then be implemented for every field that should be structurally pinned. -// Since this is user-decideable at the struct definition, this could be done with a proc-macro akin to `pin-project`. - -impl<'a, T, F: FieldClosure> Project for Pin<&'a mut T> -where - F::Field: StructurallyPinnedField, -{ - type Output = Pin<&'a mut ::Type>; - pub fn map(self, f: impl) -> Self::Output - { /* do the offsetting */ } -} - -impl<'a, T, F: FieldClosure> Project for Pin<&'a mut T> -where - F::Field: !StructurallyPinnedField, -{ - type Output = &'a mut ::Type; - pub fn map(self, f: F) -> Self::Output - { /* do the offsetting */ } -} -``` -The `#[with_negative_reasoning]` attribute on a `Trait` result in the following: -- `!Trait` can be used in where clauses. -- `T: !Trait` means that there exists an explicit `impl !Trait for T` somewhere. -- Typechk knows that `Trait` and `!Trait` are mutually exclusive and thus can identify non-overlapping impl blocks. -- When `Trait` or `!Trait` are implemented, they have to be implemented for the entire type. This behavior is similar to `Drop`. So you are not able to implement it only for `Foo<'static>`. - - -A variation of this feature is `xor` traits, where a type is only ever allowed to implement one from the given set. It could achieve the same thing, while being more flexible when one wants to define more than two different user-specified projections. - -## Alternative to negative reasoning - -One alternative is to also create an extension trait of `Field`, but then rely on the proc-macro to specify the correct projection-output type in an associated type: +Generics of the struct have to be specified and the field has to be accessible by the calling scope: ```rust -// We have to mark it `unsafe`, because the `ProjOutput` type could be wrongly specified. -pub unsafe trait PinProjectableField: Field { - type ProjOutput<'a>; // this is either `&'a mut Self::Type` or `Pin<&'a mut Self::Type>`. -} - -impl<'a, T> Pin<&'a, mut T> { - pub fn map>(self, f: F) -> ::ProjOutput<'a> - where - F::Field: PinProjectableField, - { /* do the offsetting */ } +pub mod inner { + pub struct Foo { + a: usize, + pub b: T, + } + type Ty = field_of!(Foo, a); // Compile error: expected 1 generic argument + type Ty = field_of!(Foo<()>, a); // OK + type Ty = field_of!(Foo::<()>, b); // OK } +type Ty = field_of!(Foo<()>, a); // Compile error: private field +type Ty = field_of!(Foo<()>, b); // OK +type Ty = field_of!(Foo, b); // OK ``` -This approach is used by [the field projection example from Gary](https://github.com/nbdd0121/field-projection/). - -A big issue that this approach has is that the `map` function cannot have behavior depending on the projection output. This results in practice, that `mem::transmute_copy` has to be used, since the compiler cannot prove that `ProjOutput` always has the same size. # Drawbacks [drawbacks]: #drawbacks -Adds considerable complexity. +Adds additional complexity. # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives -## Why specify projections via proc-macros? +The presented approach is designed to be minimal and extendable. The `Field` trait can be extended and additional information such as the projection output can be added. -In the design meeting the following alternative was brought up: -```rust -pub struct NotPin(pub T); - -impl Unpin for NotPin {} -``` -Instead of marking fields with `#[pin]`, they should wrap not structurally pinned fields with the `NotPin` struct. The `map` function of `Pin` would then use `!Unpin`/`Unpin` instead of the `StructurallyPinnedField` field used here. - -The problem with this solution is that now users always get reminded that the field is not structurally pinned. They have to wrap a value when they want to assign it and they have to unwrap it when they want to read it. This property is also not something for the type system, rather it is a property of that specific field of the struct. +The `field_of!` macro avoids adding special syntax to refer to a field of a type and while it is not ergonomic, this can be changed by adding syntax later. # Prior art @@ -231,7 +210,7 @@ There are some crates that enable field projections via (proc-)macros: ## Other languages -Other languages generally do not have this feature in the same extend. C++ has `shared_ptr` which allows the creation of another `shared_ptr` pointing at a field of a `shared_ptr`'s pointee. This is possible, because `shared_ptr` is made up of two pointers, one pointing to the data and another pointing at the ref count. While this is not possible to add to `Arc` without introducing a new field, it could be possible to add another `Arc` pointer that allowed field projections. See [the future possibilities section][arc-projection] for more. +Java has reflection, which gives access to type information at runtime. ## RFCs - [`ptr-to-field`](https://github.com/rust-lang/rfcs/pull/2708) @@ -249,27 +228,64 @@ Other languages generally do not have this feature in the same extend. C++ has ` # Unresolved questions [unresolved-questions]: #unresolved-questions -The whole design, please look at the PR to see the currently open questions. +None. # Future possibilities [future-possibilities]: #future-possibilities -## Operator syntax +## Use closures to improve ergonomics -Introduce a `Project` trait and a binary operator that is syntactic sugar for `Project::project($left, |f| f.$right)`. +One has to spell out the projected type for every projection. Closures could be used to make use of type inference where possible. We introduce a new closure type in `core::marker`: +```rust +pub trait FieldClosure: Fn { + type Field: Field; +} +``` +This trait is only implementable by the compiler. It is implemented for closures that +- do not capture variables +- only do a single field access on the singular parameter they have -## Project multiple field at once +Positive example: `|foo| foo.bar` +Negative examples: +- `|_| foo.bar`, captures `foo` +- `|foo| foo.bar.baz`, does two field accesses +- `|foo| foo.bar()`, calls a function +- `|foo| &mut foo.bar`, creates a reference to the field +- `|foo, bar| bar.baz`, takes two parameters + +This trait makes calling a projection function a lot more ergonomic: +```rust +wrapper.project(|i| i.field) +// Instead of: +wrapper.project::() +``` + +## Limited negative reasoning + +There is the need to make the output type of `map` functions depend on properties of the projected field. In the case of `Pin`, this is whether the field is structurally pinned or not. If it is, then the return type should be `Pin<&mut F::Type>`, if it is not, then it should be `&mut F::Type` instead. -Introduce a `FieldChainClosure` trait that is implemented for closures that contain only a field access chain `|foo| foo.bar.baz`. The chain should not contain `Box`es or other types with `deref`s, since then we could be leaving the allocation of the base struct. +Negative reasoning would allow implementing the projection function with the correct type. + + +## Operator syntax + +Introduce a `Project` trait and a binary operator that is syntactic sugar for `Project::project($left, |f| f.$right)`. This would make projections even more ergonomic: +```rust +wrapper->field +// Instead of: +wrapper.project(|i| i.field) +// or +wrapper.project::() +``` ## Support misaligned fields and `packed` structs -Create the `MaybeUnalignedField` trait that also has a constant `WELL_ALIGNED: bool`. This trait is also automatically implemented by the compiler even for packed structs. +Create the `MaybeUnalignedField` trait as a supertrait of `Field` with the constant `WELL_ALIGNED: bool`. This trait is also automatically implemented by the compiler even for packed structs. ## `enum` and `union` support Both enums and unions cannot be treated like structs, since some variants might not be currently valid. This makes these fundamentally incompatible with the code that this RFC tries to enable. They could be handled using similar traits, but these would not guarantee the same things. For example, union fields are always allowed to be uninitialized. -## Field marco attributes +## Field macro attributes To make things easier for implementing custom projections, we could create a new proc-macro kind that is placed on fields. From 6dc64d1e071d2804179cfcdf0263b18cd0e3efc6 Mon Sep 17 00:00:00 2001 From: y86-dev Date: Tue, 18 Apr 2023 09:15:28 +0200 Subject: [PATCH 23/28] Add tuple support --- text/3318-field-projection.md | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/text/3318-field-projection.md b/text/3318-field-projection.md index 105d2d766a5..7e3c64b27d6 100644 --- a/text/3318-field-projection.md +++ b/text/3318-field-projection.md @@ -62,9 +62,11 @@ This field type also implements the `Field` trait that cannot be manually implem - its own type, - the offset at which the field can be found inside of the struct. -Since the trait cannot be implemented manually, you can be sure that a type implementing it actually refers to a field: +This is also generated for tuples, so you can use `field_of!((i32, u32), 0)` to get the type describing the `i32` element. + +Since the `Field` trait cannot be implemented manually, you can be sure that a type implementing it actually refers to a field: ```rust -fn get_field>(problem: &Problem) -> &T::Type { +fn get_field>(problem: &Problem) -> &F::Type { let ptr: *const Problem = problem; // SAFETY: `F` implements the `Field` trait and thus we find `F::Type` at `F::OFFSET` inside // of `ptr` that was derived from a reference. @@ -101,7 +103,7 @@ pub struct Config { If we want to write a new config, then we always have to write the whole struct, including the `reserved` field that is comparatively big. We can avoid this by providing a field projection: ```rust impl VolatileMem { - pub fn map>(self) -> VolatileMem { + pub fn map>(self) -> VolatileMem { Self { // SAFETY: `F` implements the `Field` trait and thus we find `F::Type` at `F::OFFSET` // inside of `ptr` that is always valid. @@ -127,19 +129,19 @@ And we will not have to always overwrite `reserved` with the same data. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -For every field of every non-packed struct, the compiler creates a unique, unnameable type that represent that field. These generated types will: -- have meaningful names in error messages (e.g. `Struct::field`), +For every field of every non-packed struct and tuple, the compiler creates a unique, unnameable type that represent that field. These generated types will: +- have meaningful names in error messages (e.g. `Struct::field`, `(i32, u32)::0`), - implement the `Field` with accurate associated types and constants. The `Field` trait will reside in `core::marker` and is: ```rust /// A type representing a field on a struct. pub trait Field { - /// The type of the struct containing this field. - type Struct; + /// The type (struct or tuple) containing this field. + type Base; /// The type of this field. type Type; - /// The offset of this field from the beginning of the `Base` struct in bytes. + /// The offset of this field from the beginning of the `Base` type in bytes. const OFFSET: usize; } ``` @@ -157,28 +159,30 @@ fn project(base: &F::Base) -> &F::Type { } ``` -Importantly, the `Field` trait should only be implemented on non-`packed` structs, since otherwise the above code would not be sound. +Importantly, the `Field` trait should only be implemented on fields of non-`packed` types, since otherwise the above code would not be sound. -Users will be able to name this type by invoking the compiler built-in macro `field_of!` residing in `core`. This macro takes a struct type and an identifier/number for the accessed field: +Users will be able to name this type by invoking the compiler built-in macro `field_of!` residing in `core`. This macro takes a type and an identifier/number for the accessed field: ```rust macro_rules! field_of { ($struct:ty, $field:tt) => { /* compiler built-in */ } } ``` -Generics of the struct have to be specified and the field has to be accessible by the calling scope: +Generics of the type have to be specified and the field has to be accessible by the calling scope: ```rust pub mod inner { pub struct Foo { a: usize, pub b: T, } - type Ty = field_of!(Foo, a); // Compile error: expected 1 generic argument + type Ty = field_of!(Foo, a); // Compile error: missing generics for struct `Foo` type Ty = field_of!(Foo<()>, a); // OK type Ty = field_of!(Foo::<()>, b); // OK + type Ty = field_of!(Foo<()>, c); // Compile error: no field `c` on type `Foo<()>` } -type Ty = field_of!(Foo<()>, a); // Compile error: private field +type Ty = field_of!(Foo<()>, a); // Compile error: field `a` of struct `inner::Foo` is private type Ty = field_of!(Foo<()>, b); // OK type Ty = field_of!(Foo, b); // OK +type Ty = field_of!((T, T, i32), 1); // OK ``` # Drawbacks @@ -238,7 +242,7 @@ None. One has to spell out the projected type for every projection. Closures could be used to make use of type inference where possible. We introduce a new closure type in `core::marker`: ```rust pub trait FieldClosure: Fn { - type Field: Field; + type Field: Field; } ``` This trait is only implementable by the compiler. It is implemented for closures that From 78b8ef45a8d4e9cfcdf207aecbd09ccce2586e0c Mon Sep 17 00:00:00 2001 From: y86-dev Date: Sat, 29 Apr 2023 14:23:46 +0200 Subject: [PATCH 24/28] Address Tyler's concerns and add an `RCU` example --- text/3318-field-projection.md | 103 ++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/text/3318-field-projection.md b/text/3318-field-projection.md index 7e3c64b27d6..ec43e415144 100644 --- a/text/3318-field-projection.md +++ b/text/3318-field-projection.md @@ -40,6 +40,25 @@ where ``` Since the supplied closures are only allowed to do field projections, it would be natural to add `SAFETY` comments, but that gets even more tedious. +This feature is one important piece in the puzzle of safe and ergonomic pin projections. While it is not sufficient by itself, it enables experimentation with proc-macro-based implementations to solve the rest of the puzzle. Additionally this feature paves the way for general custom field projection which is useful for the following situations: +- volatile-only memory access (see example below), +- accessing fields of structs inside of raw pointers, `NonNull`, `Cell`, `UnsafeCell`, `MaybeUninit`, +- RCU interactions with locks (see [appendix][#rcu-interactions-with-locks]), + + +## RFC History + +This RFC went through a couple of iterations and changed considerably from the initial proposal. The problem that the author intended to solve were `Pin` projections. In the Rust support for the Linux kernel, lots of types are self referential, because they contain circular, intrusive doubly linked lists. These lists have to be pinned, since list elements own pointers to the next and previous elements. Other datastructures are also implemented this way. Overall this results in most types having to be pinned and thus we have to deal with `Pin<&mut T>` constantly. Whenever one wants to access a field, they have to use the `unsafe` projection functions. + +The currently preferred solution for this problem from the Rust ecosystem are [pin-project] and [pin-project-lite]. These are however unsuitable for use in the kernel. +- [pin-project] cannot be used, since it requires `syn` and that is currently not used by the kernel which would add over 50k lines of Rust code. +- [pin-project-lite] does not depend on `syn`, but it has other problems: error messages are not useful, some use cases are not supported. Additionally the declarative macro is very complex and would be difficult to maintain. + +Also, these solutions require the user to write `let this = self.project();` at the beginning of every function where one wants to project. + +While exploring this problem, the Rust-for-Linux team discovered that they would like to use custom projections for using RCU together with locks. The author also discovered that these projections could be useful for other types. + +The very first design only supported transparent wrapper types (i.e. only with a single field). The current version is the most general and minimal of all of the earlier designs. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation @@ -223,6 +242,7 @@ Java has reflection, which gives access to type information at runtime. - https://internals.rust-lang.org/t/cell-references-and-struct-layout/11564 [pin-project]: https://crates.io/crates/pin-project +[pin-project-lite]: https://crates.io/crates/pin-project-lite [field-project]: https://crates.io/crates/field-project [cell-project]: https://crates.io/crates/cell-project [pin-projections]: https://crates.io/crates/pin-projections @@ -264,6 +284,7 @@ wrapper.project(|i| i.field) wrapper.project::() ``` + ## Limited negative reasoning There is the need to make the output type of `map` functions depend on properties of the projected field. In the case of `Pin`, this is whether the field is structurally pinned or not. If it is, then the return type should be `Pin<&mut F::Type>`, if it is not, then it should be `&mut F::Type` instead. @@ -290,6 +311,88 @@ Create the `MaybeUnalignedField` trait as a supertrait of `Field` with the const Both enums and unions cannot be treated like structs, since some variants might not be currently valid. This makes these fundamentally incompatible with the code that this RFC tries to enable. They could be handled using similar traits, but these would not guarantee the same things. For example, union fields are always allowed to be uninitialized. +If enums had variant types, then these variant types could be easily supported, as they are essentially just structs. Unions could generally support the `Field` trait, but calling `field_of!(Union, field)` must be `unsafe`, since projections might rely on the field being active. + ## Field macro attributes To make things easier for implementing custom projections, we could create a new proc-macro kind that is placed on fields. + +## Make the `Field` trait `unsafe` and implementable by users + +We could make the `Field` trait `unsafe` and allow custom implementations. + +# Appendix + +## Field projections for Rust-for-Linux + +### RCU interactions with locks + +RCU (read-copy-update) is a special kind of synchronization mechanism used in the Linux kernel. It is mainly used with data structures based on pointers. These pointers need to be explicitly annotated (even in C). Further, the data structure must be constructed such that elements can be added/removed via a single atomic operation (e.g. swapping a pointer). + +Readers and writers can access the data structure concurrently. Readers need to acquire the read lock before they read any RCU pointers. Writers can freely swap the pointers atomically, but when they want to free any objects that were in the data structure protected by RCU, they need to call `rcu_synchronize`. This function waits for all readers to relinquish any currently held locks, since then no reader can access the removed object. After `rcu_synchronize` returns, the writer can free the object. + +The motivation for using RCU is performance. The way RCU is implemented in the kernel, acquiring the read lock is extremely cheap, it does not involve any atomics and in some cases even is a no-op. Additionally it never needs to wait. If writes are rare and reads are common, this improves performance significantly. To learn more about RCU, you can read [this](https://lwn.net/Articles/262464/) article. + +Now onto a simple example that shows how using RCU could look like in Rust. In the example, we have a `Process` struct that contains a file descriptor table (fdt). This table is stored in a different allocation (via a `Box`) and the `Rcu` pointer wrapper struct marks that this pointer can only be accessed via RCU operations. Access to an instance of `Process` is serialized via a `Mutex`. But certain operations have to be executed very often and so locking and unlocking the mutex can get very expensive. In our case, we want to optimize fetching the current length of the FDT. +```rust +pub struct Process { + fdt: Rcu>, + id: usize, + // other fields ... +} + +pub struct FileDescriptorTable { + len: usize, + // other implementation details not important +} + +impl Process { + pub fn current() -> &'static RcuLock> { todo!() } + + // Note the parameter type, an RcuLock is a lock wrapper providing + // Rcu support for some lock types. + pub fn get_fdt_len(self: &RcuLock>) -> usize { + // RcuLock only allows projections to fields of type `Rcu`. + let fdt: &Rcu> = RcuLock::project::(self); + // Next we acquire the read lock. + let rcu_guard: RcuGuard = rcu::read_lock(); + // To read an `Rcu` pointer, we need an `RcuGuard`. + let fdt: &FDT = fdt.get(&rcu_guard); + let len = fdt.len; + // Dropping it explicitly ends the borrow of `fdt`. + drop(rcu_guard); + len + } + + pub fn id(self: &RcuLock>) -> usize { + // When reading/writing a normal field, we have to use the `Mutex`: + let guard: RcuLockGuard> = self.lock(); + // We cannot give out `&mut` to `Rcu` fields, since those are always + // accessible immutably via the RcuLock projections, so we again have to + // rely on projections here to guarantee soundness. + let id: &mut usize = guard.project::(); + *id + } + + pub fn replace_fdt(self: &RcuLock>, new_fdt: Box) { + // We again obtain the `Rcu` pointer: + let fdt: &Rcu> = RcuLock::project::(self); + // When we want to overwrite an `Rcu` pointer, we can do so by just calling `set`: + let old: RcuOldValue> = fdt.set(new_fdt); + // Since readers might still be reading the old value (we have not yet called + // `rcu_synchronize`) we must hold onto this object until we call `rcu_synchronize`. + // However reading the old value is fine (i.e. `RcuOldValue` implements `Deref`): + let len = old.len; + // But it does not implement `DerefMut`. On dropping an `RcuOldValue`, `rcu_synchronize` + // is called. But we can also get the old value out now: + let old: Box = old.sync(); // this will call `rcu_synchronize`. + } + + // When the caller already owns the Rcu lock, then we can give out our FDT. Note that + // the lifetime of the returned reference is the same as the guard and not of `self`. + pub fn get_fdt<'a>(self: &RcuLock>, guard: &'a RcuGuard) -> &'a FDT { + let fdt: &Rcu> = RcuLock::project::(self); + fdt.get(&guard) + } +} +``` From 1a0d568acf2187ae3c525feb9de51bb50fdc1576 Mon Sep 17 00:00:00 2001 From: y86-dev Date: Sat, 29 Apr 2023 20:10:44 +0200 Subject: [PATCH 25/28] Add `Struct::field#foo` section to future possibilities --- text/3318-field-projection.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/text/3318-field-projection.md b/text/3318-field-projection.md index ec43e415144..6a57f3de44e 100644 --- a/text/3318-field-projection.md +++ b/text/3318-field-projection.md @@ -283,7 +283,9 @@ wrapper.project(|i| i.field) // Instead of: wrapper.project::() ``` +## Refer to fields via `Struct::field#foo` +We could make `Struct::field#foo` be equivalent to `field_of!(Struct, foo)`. In cases where there is no ambiguity, it would just be `Struct::foo`. ## Limited negative reasoning From 72447043b253e98ca7bf0ea3089ea18b3e948bf3 Mon Sep 17 00:00:00 2001 From: y86-dev Date: Sun, 30 Apr 2023 09:24:47 +0200 Subject: [PATCH 26/28] Add paragraph about unsized fields not being supported --- text/3318-field-projection.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/text/3318-field-projection.md b/text/3318-field-projection.md index 6a57f3de44e..20d578f71fc 100644 --- a/text/3318-field-projection.md +++ b/text/3318-field-projection.md @@ -180,6 +180,8 @@ fn project(base: &F::Base) -> &F::Type { Importantly, the `Field` trait should only be implemented on fields of non-`packed` types, since otherwise the above code would not be sound. +Another restriction is that unsized fields have dynamic offsets and thus cannot be statically known. So these fields types do not implement the `Field` trait, but the compiler generated type for the field still exists. + Users will be able to name this type by invoking the compiler built-in macro `field_of!` residing in `core`. This macro takes a type and an identifier/number for the accessed field: ```rust macro_rules! field_of { From 37d267cce6ef4e3e112b61e1f138c55f6d10a972 Mon Sep 17 00:00:00 2001 From: y86-dev Date: Wed, 3 May 2023 09:49:03 +0200 Subject: [PATCH 27/28] add links to rfcs and issues for negative reasoning --- text/3318-field-projection.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/text/3318-field-projection.md b/text/3318-field-projection.md index 20d578f71fc..0aeecb9e73e 100644 --- a/text/3318-field-projection.md +++ b/text/3318-field-projection.md @@ -295,6 +295,11 @@ There is the need to make the output type of `map` functions depend on propertie Negative reasoning would allow implementing the projection function with the correct type. +There have been some earlier RFCs about this topic. These cover more than is needed for making projections work: +- #1148 +- #586 + +More information is also found in issue #1053. ## Operator syntax From 032ab649987f7b299351920498e555257e5c9b89 Mon Sep 17 00:00:00 2001 From: y86-dev Date: Wed, 3 May 2023 09:56:50 +0200 Subject: [PATCH 28/28] Change union support --- text/3318-field-projection.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/3318-field-projection.md b/text/3318-field-projection.md index 0aeecb9e73e..4263c9dcf14 100644 --- a/text/3318-field-projection.md +++ b/text/3318-field-projection.md @@ -320,7 +320,9 @@ Create the `MaybeUnalignedField` trait as a supertrait of `Field` with the const Both enums and unions cannot be treated like structs, since some variants might not be currently valid. This makes these fundamentally incompatible with the code that this RFC tries to enable. They could be handled using similar traits, but these would not guarantee the same things. For example, union fields are always allowed to be uninitialized. -If enums had variant types, then these variant types could be easily supported, as they are essentially just structs. Unions could generally support the `Field` trait, but calling `field_of!(Union, field)` must be `unsafe`, since projections might rely on the field being active. +If enums had variant types, then these variant types could be easily supported, as they are essentially just structs. + +For unions we could add a supertrait of `Field` named `MaybeUninitField` that is implemented instead of the `Field` trait. Projection authors now can choose to allow these where it makes sense (e.g. `MaybeUninit`). ## Field macro attributes