Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor unsizing coercion documentation #1731

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/expressions/method-call-expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ The following procedure is used:

r[expr.method.candidate-receivers]
The first step is to build a list of candidate receiver types.
Obtain these by repeatedly [dereferencing][dereference] the receiver expression's type, adding each type encountered to the list, then finally attempting an [unsized coercion] at the end, and adding the result type if that is successful.
Obtain these by repeatedly [dereferencing][dereference] the receiver expression's type, adding each type encountered to the list, then finally attempting an [unsizing coercion][coerce.unsize] at the end, and adding the result type if that is successful.

r[expr.method.candidate-receivers-refs]
Then, for each candidate `T`, add `&T` and `&mut T` to the list immediately after `T`.

For instance, if the receiver has type `Box<[i32;2]>`, then the candidate types will be `Box<[i32;2]>`, `&Box<[i32;2]>`, `&mut Box<[i32;2]>`, `[i32; 2]` (by dereferencing), `&[i32; 2]`, `&mut [i32; 2]`, `[i32]` (by unsized coercion), `&[i32]`, and finally `&mut [i32]`.
For instance, if the receiver has type `Box<[i32;2]>`, then the candidate types will be `Box<[i32;2]>`, `&Box<[i32;2]>`, `&mut Box<[i32;2]>`, `[i32; 2]` (by dereferencing), `&[i32; 2]`, `&mut [i32; 2]`, `[i32]` (by unsizing coercion), `&[i32]`, and finally `&mut [i32]`.

r[expr.method.candidate-search]
Then, for each candidate type `T`, search for a [visible] method with a receiver of that type in the following places:
Expand Down Expand Up @@ -101,5 +101,4 @@ These cases require a [disambiguating function call syntax] for method and funct
[disambiguating function call syntax]: call-expr.md#disambiguating-function-calls
[dereference]: operator-expr.md#the-dereference-operator
[methods]: ../items/associated-items.md#methods
[unsized coercion]: ../type-coercions.md#unsized-coercions
[`IntoIterator`]: std::iter::IntoIterator
193 changes: 156 additions & 37 deletions src/type-coercions.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,15 @@ r[coerce.types.ref-to-pointer]
r[coerce.types.mut-to-pointer]
* `&mut T` to `*mut T`

r[coerce.types.unsize]
* `T` to `U` if `T: CoerceUnsized<U>`. For example:
```rust
const _: &dyn std::fmt::Display = &0u8; // &u8 -> &dyn Display
const _: &[u32] = &[0, 1, 2, 3, 4, 5]; // &[u32; 4] -> &[u32]
```

See [unsizing coercion](#unsizing-coercions) for more details.

r[coerce.types.deref]
* `&T` or `&mut T` to `&U` if `T` implements `Deref<Target = U>`. For example:

Expand Down Expand Up @@ -163,20 +172,6 @@ r[coerce.types.deref]
r[coerce.types.deref-mut]
* `&mut T` to `&mut U` if `T` implements `DerefMut<Target = U>`.

r[coerce.types.unsize]
* TyCtor(`T`) to TyCtor(`U`), where TyCtor(`T`) is one of
- `&T`
- `&mut T`
- `*const T`
- `*mut T`
- `Box<T>`

and where `U` can be obtained from `T` by [unsized coercion](#unsized-coercions).

<!--In the future, coerce_inner will be recursively extended to tuples and
structs. In addition, coercions from subtraits to supertraits will be
added. See [RFC 401] for more details.-->

r[coerce.types.fn]
* Function item types to `fn` pointers

Expand All @@ -187,43 +182,165 @@ r[coerce.types.never]
* `!` to any `T`

r[coerce.unsize]
### Unsized Coercions
### Unsizing Coercions

r[coerce.unsize.intro]
The following coercions are called `unsized coercions`, since they
relate to converting types to unsized types, and are permitted in a few
cases where other coercions are not, as described above. They can still happen
anywhere else a coercion can occur.
The following coercions are called "Unsizing coercions", since their targets contain an unsized type.
Unsizing coercions apply to pointer-like types which point to types which can lose some of their compile-time known information (such as size or implemented traits). For example:

```rust
use std::cell::Cell;

fn main() {
// `&[u8; 0]` can be coerced to `&[u8]`.
//
// here `&_` is the pointer-like type,
// `[u8; 0]` is the original pointee,
// and `[u8]` is more erased pointee (it lost the length information).
let _: &[u8] = &[];

trait A: Super {}
impl A for () {}

trait Super {}
impl Super for () {}

// `&()` can be coerced to `&dyn A`, losing the type information.
let a: &dyn A = &();

// `&dyn A` can be coerced to `&dyn Super`,
// loosing the fact that the underlying type (unit) implements `A` too.
let _: &dyn Super = a;

// The same coercions work with other pointer-like types and wrappers over them:
let _: Box<[u8]> = Box::<[u8; 0]>::new([]);
let _: Cell<Box<[u8]>> = Cell::new(Box::<[u8; 0]>::new([]));

// The result of the coercion doesn't *have* to be the same pointer-like type,
// alhtough this is only allowed for certain pairs of pointer-like types.
let _: *const dyn A = &mut ();
}
```

r[coerce.unsize.confusion]
> [!NOTE]
> The term "unsizing" might be quite confusing, since the coercion works on sized types (pointers) and the source pointer might point to an unsized type in the first place (`&dyn A -> &dyn Super` in the example above).
>
> "Unsizing" refers to the main purpose of these coercions --- converting (pointers to) sized types to (pointers to) unsized types. The pointers being not the focus, since unsized types can't exist without them.

r[coerce.unsize.metadata]
When performing unsizing coercion, the pointer metadata type changes. For example, when unsizing `&u32` to `&dyn Debug` metadate type changes from `()` to `DynMetadata<dyn Debug>` (note that exact metadata types are not yet stable). This can also lead to a change in the pointer size -- `&u32` is half the size of `&dyn Debug`.

r[coerce.unsize.traits]
Three traits, [`Unsize`], [`CoerceUnsized`], and [`PinCoerceUnsized`] are used to assist in this process and expose it for library use.

r[coerce.unsize.traits.unsize]
[`Unsize`] represents the fact that the target type is layout compatible with the source type and the pointer metadata of the target type can be derived from the metadata of the source, meaning that a pointer to the source type can be converted to a pointer to the target type. For example `[T; N]` implements `Unsize<[T]>` meaning that you can *unsize* former into the later, allowing coercions such as `&[T; N] -> &[T]`.

r[coerce.unsize.traits.coerce-unsized]
[`CoerceUnsized`] represents the fact that a pointer-like type can be coerced to another pointer-like type, due to `Unsize` being implemented for their pointees. For example, `&T` implements `CoerceUnsized<&U>` when `T: Unsize<U>`.

r[coerce.unsize.traits.pin-coerce-unsized]
[`PinCoerceUnsized`] is an unsafe marker trait for pointer-like types unsizing coercion of which does not break [`Pin`] guarantees. It is a requirement of the [`CoerceUnsized` implementation for `Pin`][coerce.unsize.coerce-unsized-impls.pin-pin]. That is, `&D: PinCoerceUnsized` implies `Pin<&T>: CoerceUnsized<Pin<&U>>`.

r[coerce.unsize.trait]
Two traits, [`Unsize`] and [`CoerceUnsized`], are used
to assist in this process and expose it for library use. The following
coercions are built-ins and, if `T` can be coerced to `U` with one of them, then
an implementation of `Unsize<U>` for `T` will be provided:
The following implementations of [`Unsize`] are built-in:

r[coerce.unsize.slice]
* `[T; n]` to `[T]`.
* `[T; n]: Unsize<[T]>`.

r[coerce.unsize.trait-object]
* `T` to `dyn U`, when `T` implements `U + Sized`, and `U` is [dyn compatible].
* `T: Unsize<dyn U>`, when `T` implements `U + Sized`, and `U` is [dyn compatible].

r[coerce.unsize.trait-upcast]
* `dyn T` to `dyn U`, when `U` is one of `T`'s [supertraits].
* `dyn T: Unsize<dyn U>`, when `U` is one of `T`'s [supertraits].
* This allows dropping auto traits, i.e. `dyn T + Auto` to `dyn U` is allowed.
* This allows adding auto traits if the principal trait has the auto trait as a super trait, i.e. given `trait T: U + Send {}`, `dyn T` to `dyn T + Send` or to `dyn U + Send` coercions are allowed.

r[coerce.unsized.composite]
* `Foo<..., T, ...>` to `Foo<..., U, ...>`, when:
* `Foo` is a struct.
r[coerce.unsize.composite]
* `S<..., T, ...>: Unsize<S<..., U, ...>>`, when:
* `S` is a struct.
* `T` implements `Unsize<U>`.
* The last field of `Foo` has a type involving `T`.
* If that field has type `Bar<T>`, then `Bar<T>` implements `Unsize<Bar<U>>`.
* T is not part of the type of any other fields.
* The last field of `S` has a type involving `T`. i.e. it's either of `T` or `C<..., T, ...>` where `C` is a type constructor and `T` is only present in it once (`C<T, T>` is disallowed).
* The last field is the *only* field which type involves `T`.
* The type of the last field implements `Unsize<F>` where `F` is the same type with `T` replaced by `U`. i.e. if the field has type `Bar<T>`, then `Bar<T>` implements `Unsize<Bar<U>>`.

r[coerce.unsize.pointer]
Additionally, a type `Foo<T>` can implement `CoerceUnsized<Foo<U>>` when `T` implements `Unsize<U>` or `CoerceUnsized<U>`. This allows it to provide an unsized coercion to `Foo<U>`.

<!-- FIXME: are there more requirements for `CoerceUnsized`? -->

r[coerce.unsize.coerce-unsized-impls]
Types which currently implement `CoerceUnsized<_>` (assuming `T: Unsize<U>`, `'a: 'b`, `A: CoerceUnsized<B>`, `Al: Allocator`):
Comment on lines +270 to +273
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally I would prefer to not duplicate the list of impls from the standard library here. I assume the user can click the link to CoerceUnsized to see which impls it has?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, mh. Yes, users can find them in the std docs (that's where I got the impls from!).

However, if we want the reference to be prescriptive, I think we should include this. While this is library code, it controls how a language feature behaves.

But I don't have a strong opinion here, if you think that this is not useful I can remove the list.


r[coerce.unsize.coerce-unsized-impls.ref-ref]
* `&'a T: CoerceUnsized<&'b U>`

r[coerce.unsize.coerce-unsized-impls.refmut-ref]
* `&'a mut T: CoerceUnsized<&'b U>`

r[coerce.unsize.coerce-unsized-impls.ref-constptr]
* `&T: CoerceUnsized<*const U>`

r[coerce.unsize.coerce-unsized-impls.refmut-constptr]
* `&mut T: CoerceUnsized<*const U>`

r[coerce.unsize.coerce-unsized-impls.refmut-mutptr]
* `&mut T: CoerceUnsized<*mut U>`

r[coerce.unsize.coerce-unsized-impls.refmut-refmut]
* `&'a mut T: CoerceUnsized<&'a mut U>`
* Note: the lifetimes of source and target must match, which is different from the impls where the target is a [shared reference][type.pointer.reference.shared].

r[coerce.unsize.coerce-unsized-impls.cellref-cellref]
* `core::cell::Ref<'a, A>: CoerceUnsized<core::cell::Ref<'a, B>>`

r[coerce.unsize.coerce-unsized-impls.cellrefmut-cellrefmut]
* `core::cell::RefMut<'a, A>: CoerceUnsized<core::cell::RefMut<'a, B>>`

r[coerce.unsize.coerce-unsized-impls.pin-pin]
* `Pin<A>: CoerceUnsized<Pin<B>>` where `A: PinCoerceUnsized, B: PinCoerceUnsized`

r[coerce.unsize.coerce-unsized-impls.constptr-constptr]
* `*const T: CoerceUnsized<*const U>`

r[coerce.unsize.coerce-unsized-impls.mutptr-constptr]
* `*mut T: CoerceUnsized<*const U>`

r[coerce.unsize.coerce-unsized-impls.mutptr-mutptr]
* `*mut T: CoerceUnsized<*mut U>`

r[coerce.unsize.coerce-unsized-impls.cell-cell]
* `core::cell::Cell<A>: CoerceUnsized<core::cell::Cell<B>>`

r[coerce.unsize.coerce-unsized-impls.refcell-refcell]
* `RefCell<A>: CoerceUnsized<RefCell<B>>`

r[coerce.unsize.coerce-unsized-impls.syncunsafecell-syncunsafecell]
* `SyncUnsafeCell<A>: CoerceUnsized<SyncUnsafeCell<B>>`

r[coerce.unsize.coerce-unsized-impls.unsafecell-unsafecell]
* `UnsafeCell<A>: CoerceUnsized<UnsafeCell<B>>`

r[coerce.unsize.coerce-unsized-impls.nonnull-nonnull]
* `NonNull<T>: CoerceUnsized<NonNull<U>>`

r[coerce.unsize.coerce-unsized-impls.box-box]
* `Box<T, Al>: CoerceUnsized<Box<U, Al>>`

r[coerce.unsize.coerce-unsized-impls.rc-rc]
* `Rc<T, Al>: CoerceUnsized<Rc<U, Al>>`

r[coerce.unsize.coerce-unsized-impls.uniquerc-unieuqrc]
* `UniqueRc<T, Al>: CoerceUnsized<UniqueRc<U, Al>>`

r[coerce.unsize.coerce-unsized-impls.rcweak-rcweak]
* `alloc::rc::Weak<T, Al>: CoerceUnsized<alloc::rc::Weak<U, Al>>`

r[coerce.unsize.coerce-unsized-impls.arc-arc]
* `Arc<T, Al>: CoerceUnsized<Arc<U, Al>>`

r[coerce.unsized.pointer]
Additionally, a type `Foo<T>` can implement `CoerceUnsized<Foo<U>>` when `T`
implements `Unsize<U>` or `CoerceUnsized<Foo<U>>`. This allows it to provide an
unsized coercion to `Foo<U>`.
r[coerce.unsize.coerce-unsized-impls.arcweak-arcweak]
* `alloc::sync::Weak<T, Al>: CoerceUnsized<alloc::sync::Weak<U, Al>>`

> [!NOTE]
> While the definition of the unsized coercions and their implementation has been stabilized, the traits themselves are not yet stable and therefore can't be used directly in stable Rust.
Expand Down Expand Up @@ -323,6 +440,8 @@ precisely.
[subtype]: subtyping.md
[dyn compatible]: items/traits.md#dyn-compatibility
[type cast operator]: expressions/operator-expr.md#type-cast-expressions
[`Pin`]: std::pin::Pin
[`PinCoerceUnsized`]: std::pin::PinCoerceUnsized
[`Unsize`]: std::marker::Unsize
[`CoerceUnsized`]: std::ops::CoerceUnsized
[method-call expressions]: expressions/method-call-expr.md
Expand Down