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

Arbitrary self types v2: update reference. #1699

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
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
21 changes: 17 additions & 4 deletions src/expressions/method-call-expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,17 @@ When looking up a method call, the receiver may be automatically dereferenced or
This requires a more complex lookup process than for other functions, since there may be a number of possible methods to call.
The following procedure is used:

The first step is to build a list of candidate receiver types.
The first step is to build a list of types where we might find methods.
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.
Then, for each candidate `T`, add `&T` and `&mut T` to the list immediately after `T`.
Then, for each candidate `T`, add `&T` and `&mut T` to the list immediately after `T`. While dereferencing, we don't use the normal `Deref` trait, but instead the `Receiver` trait: there is a blanket implementation of `Receiver` for all `T: Deref` so in practice this is often the same.

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 contributing 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]`.

Then, for each candidate type `T`, search for a [visible] method with a receiver of that type in the following places:
Some custom smart pointers may implement `Receiver` but not `Deref`. Imagine `MySmartPtr<T>: Receiver<Target=T>`: if the receiver type has `Box<MySmartPtr<SomeStruct>>` the contributing types will be `Box<MySmartPtr<SomeStruct>>`, `MySmartPtr<SomeStruct>` and `SomeStruct`.

Even though we assemble this list by following the chain of `Receiver` implementations, we keep a note of which steps can be reached by following the regular `Deref` chain. In the above example, that would be all but the last step. The items in the list reachable by the `Deref` chain are termed the "candidate types"; the full list reachable by the `Receiver` chain is called the "contributing types".

Then, for each _contributing_ type `T`, search for a [visible] method with a receiver of any _candidate_ type in the following places:
Comment on lines +20 to +30
Copy link
Contributor

@traviscross traviscross Jan 28, 2025

Choose a reason for hiding this comment

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

This is better, but still some things we can improve:

  • We're inconsistently using the word "candidate". Toward the top, we talk about assembling the list of candidates (i.e. "for each candidate T, add ... to the list"), but we don't mean the list of "candidate types" as defined later, we mean the list of "contributing types". The word "candidate" is also used further below in a seemingly-inconsistent way.
  • We separately define this notion of "candidate types" from "contributing types", but we don't say why or use the list of candidate types for anything as far as I can tell. It's a bit odd to define this separately if it has no semantic relevance to the language. If there is some semantic relevance, we should describe it. If it's only of implementation relevance, then it's probably a better fit for the dev guide.
  • We're no longer stating our terms upfront. That's what the bit you removed about "The first step is to build a list of candidate receiver types" was doing -- it introduced that term, then described how we built that list. The text now says "the contributing types will be..." without first introducing that this is a special term and we're building a list of these things.
  • Talking about repeated dereferencing and then saying, "well, really it's not dereferencing but this other thing" is a bit odd. It's probably better to just directly describe that we're following the chain of receiver targets.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed with all your points other than this one:

We separately define this notion of "candidate types" from "contributing types", but we don't say why or use the list of candidate types for anything as far as I can tell. It's a bit odd to define this separately if it has no semantic relevance to the language. If there is some semantic relevance, we should describe it. If it's only of implementation relevance, then it's probably a better fit for the dev guide.

It is of semantic relevance - it says this, which you may have overlooked:

Then, for each contributing type T, search for a visible method with a receiver of any candidate type in the following places:

I'll have another crack at it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Great, thanks. Did miss that bit.


1. `T`'s inherent methods (methods implemented directly on `T`).
1. Any of the methods provided by a [visible] trait implemented by `T`.
Expand Down Expand Up @@ -66,6 +70,15 @@ Once a method is looked up, if it can't be called for one (or more) of those rea
If a step is reached where there is more than one possible method, such as where generic methods or traits are considered the same, then it is a compiler error.
These cases require a [disambiguating function call syntax] for method and function invocation.

As well as emitting methods for multiple candidates within a given step,
an additional search may be performed to look for specific cases where an outer
(smart pointer) type may have a method that shadows or overrides a method
on its referent. This process is performed if we are about to return a method
identified by a by-value step; a search is then performed for a matching by-reference
methods deeper along the chain of contributing types with an identical `self` type.
This extra search is also performed if we are about to return a method from
a `&T` pick; error are emitted if a `&mut T` method would be shadowed.

> **Edition differences**: Before the 2021 edition, during the search for visible methods, if the candidate receiver type is an [array type], methods provided by the standard library [`IntoIterator`] trait are ignored.
>
> The edition used for this purpose is determined by the token representing the method name.
Expand Down
14 changes: 5 additions & 9 deletions src/items/associated-items.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,15 +110,11 @@ well as the usual function call notation.

r[items.associated.fn.method.self-ty]
If the type of the `self` parameter is specified, it is limited to types resolving
to one generated by the following grammar (where `'lt` denotes some arbitrary
lifetime):
to a type implementing the [`Receiver`] trait with a `Target` associated type
matching the implementing type. Typically, this means the type itself, a
reference to it, or a smart pointer referring to it (such as [`Box<Self>`]
or `Arc<Self>`).

```text
P = &'lt S | &'lt mut S | Box<S> | Rc<S> | Arc<S> | Pin<P>
S = Self | P
```

The `Self` terminal in this grammar denotes a type resolving to the implementing type.
This can also include the contextual type alias `Self`, other type aliases,
or associated type projections resolving to the implementing type.

Expand Down Expand Up @@ -559,11 +555,11 @@ fn main() {
[_OuterAttribute_]: ../attributes.md
[_TypeAlias_]: type-aliases.md
[_Visibility_]: ../visibility-and-privacy.md
[`Arc<Self>`]: ../special-types-and-traits.md#arct
[`Box<Self>`]: ../special-types-and-traits.md#boxt
[`Pin<P>`]: ../special-types-and-traits.md#pinp
[`Rc<Self>`]: ../special-types-and-traits.md#rct
[`Sized`]: ../special-types-and-traits.md#sized
[`Receiver`]: ../special-types-and-traits.md#receiver
[traits]: traits.md
[type aliases]: type-aliases.md
[inherent implementations]: implementations.md#inherent-implementations
Expand Down
12 changes: 5 additions & 7 deletions src/items/traits.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,13 @@ r[items.traits.dyn-compatible.associated-functions]
* Dispatchable functions must:
* Not have any type parameters (although lifetime parameters are allowed).
* Be a [method] that does not use `Self` except in the type of the receiver.
* Have a receiver with one of the following types:
* Have a receiver implementing [`Receiver`], for example one of the following types:
* `&Self` (i.e. `&self`)
* `&mut Self` (i.e `&mut self`)
* [`Box<Self>`]
* [`Rc<Self>`]
* [`Arc<Self>`]
* [`Pin<P>`] where `P` is one of the types above
* `Rc<Self>`
* `Arc<Self>`
* `Pin<P>` where `P` is one of the types above
* Not have an opaque return type; that is,
* Not be an `async fn` (which has a hidden `Future` type).
* Not have a return position `impl Trait` type (`fn example(&self) -> impl Trait`).
Expand Down Expand Up @@ -383,10 +383,8 @@ fn main() {
[trait implementation]: implementations.md#trait-implementations
[`Send`]: ../special-types-and-traits.md#send
[`Sync`]: ../special-types-and-traits.md#sync
[`Arc<Self>`]: ../special-types-and-traits.md#arct
[`Box<Self>`]: ../special-types-and-traits.md#boxt
[`Pin<P>`]: ../special-types-and-traits.md#pinp
[`Rc<Self>`]: ../special-types-and-traits.md#rct
[`Receiver`]: ../special-types-and-traits.md#receiver
[`async`]: functions.md#async-functions
[`const`]: functions.md#const-functions
[type namespace]: ../names/namespaces.md
Expand Down
37 changes: 14 additions & 23 deletions src/special-types-and-traits.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,12 @@ r[lang-types.box.deref]
* The [dereference operator] for `Box<T>` produces a place which can be moved
from. This means that the `*` operator and the destructor of `Box<T>` are
built-in to the language.

r[lang-types.box.receiver]
* [Methods] can take `Box<Self>` as a receiver.

r[lang-types.box.fundamental]
* A trait may be implemented for `Box<T>` in the same crate as `T`, which the
[orphan rules] prevent for other generic types.

<!-- Editor Note: This is nowhere close to an exhaustive list -->

r[lang-types.rc]
## `Rc<T>`

r[lang-types.rc.receiver]
[Methods] can take [`Rc<Self>`] as a receiver.

r[lang-types.arc]
## `Arc<T>`

r[lang-types.arc.receiver]
[Methods] can take [`Arc<Self>`] as a receiver.

r[lang-types.pin]
## `Pin<P>`

r[lang-types.pin.receiver]
[Methods] can take [`Pin<P>`] as a receiver.

r[lang-types.unsafe-cell]
## `UnsafeCell<T>`

Expand Down Expand Up @@ -73,7 +51,19 @@ r[lang-types.deref]
## `Deref` and `DerefMut`

As well as overloading the unary `*` operator, [`Deref`] and [`DerefMut`] are
also used in [method resolution] and [deref coercions].
also used in [deref coercions]; see also [`Receiver`] below.

r[lang-types.receiver]
## `Receiver`

[`Receiver`] is used in [method resolution]. It indicates that a type may be
used as a method receiver; that is, the type of a `self` parameter for a
method. There is a blanket implementation of `Receiver` for all `T: Deref`,
so it's rare to implement `Receiver` directly: you'd only normally do this
for smart pointer types which for some reason can't implement `Deref`.
Comment on lines +62 to +63
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd probably remove this bit about it being rare and instead describe affirmatively why Receiver can be implemented but Deref cannot in some cases.

Built-in types which implement `Receiver` (via `Deref`) and are commonly
used as method receivers include `Rc<T>`, `Arc<T>`, `Box<T>`, and `Pin<P>`
where `P: Deref`.

r[lang-types.drop]
## `Drop`
Expand Down Expand Up @@ -218,6 +208,7 @@ These implicit `Sized` bounds may be relaxed by using the special `?Sized` bound
[`DerefMut`]: std::ops::DerefMut
[`Pin<P>`]: std::pin::Pin
[`Rc<Self>`]: std::rc::Rc
[`Receiver`]: std::ops::Receiver
[`RefUnwindSafe`]: std::panic::RefUnwindSafe
[`Termination`]: std::process::Termination
[`UnwindSafe`]: std::panic::UnwindSafe
Expand Down
Loading