Skip to content

Commit 113c8bd

Browse files
committed
Update reference & deshadowing.
This changes the RFC in two significant ways: * As requested widely, it now proposes that we implement Receiver for NonNull<T> and Weak<T>. This requires us, for the first time, to add explicit code to spot potentially shadowed methods and avoid such shadowing. This is described. * It expands the Reference section to describe changes to the probing algorithm, which are now a little more extensive than the previous version of the RFC described, because we now search two different chains - one for types into which the receiver can be converted, and another chain for locations to search for possible methods.
1 parent 8b2b6df commit 113c8bd

File tree

1 file changed

+79
-10
lines changed

1 file changed

+79
-10
lines changed

text/3519-arbitrary-self-types-v2.md

Lines changed: 79 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -228,15 +228,46 @@ where
228228

229229
(See [alternatives](#no-blanket-implementation) for discussion of the tradeoffs here.)
230230

231-
It is also implemented for `&T`, `&mut T`, `*const T` and `*mut T`.
231+
It is also implemented for `&T`, `&mut T`, `Weak<T>`, `NonNull<T>`, `*const T` and `*mut T`.
232232

233-
## Compiler changes
233+
## Compiler changes: method probing
234234

235-
The existing Rust [reference section for method calls describes the algorithm for assembling method call candidates](https://doc.rust-lang.org/reference/expressions/method-call-expr.html). This algorithm changes in one simple way: instead of dereferencing types (using the `Deref<Target=T>`) trait, we use the new `Receiver<Target=T>` trait to determine the next step.
235+
The existing Rust [reference section for method calls describes the algorithm for assembling method call candidates](https://doc.rust-lang.org/reference/expressions/method-call-expr.html), and there's more detail in the [rustc dev guide](https://rustc-dev-guide.rust-lang.org/method-lookup.html).
236236

237-
(Note that the existing algorithm isn't quite as simple as following the chain of `Deref<Target=T>`. In particular, `&T` and `&mut T` are considered as candidates too at each step; this RFC does not change that.)
237+
The key part of the first page is this:
238238

239-
Because a blanket implementation is provided for users of the `Deref` trait and for `&T`/`&mut T`, the net behavior is similar. But this provides the opportunity for types which can't implement `Deref` to act as method receivers.
239+
> Then, for each candidate type `T`, search for a visible method with a receiver of that type in the following places:
240+
> - `T`'s inherent methods (methods implemented directly on `T`).
241+
> Any of the methods provided by a visible trait implemented by `T`.
242+
243+
This changes.
244+
245+
The list of candidate types is assembled in exactly the same way, but we now search for a visible method with a receiver of that type in _more_ places.
246+
247+
Specifically, instead of using the list of candidate types assembled using the `Deref` trait, we search a list assembled using the `Receiver` trait. As `Receiver` is implemented for all types that implement `Deref`, this is a longer list.
248+
249+
It's particularly important to emphasize that the list of candidate receiver types _does not change_ - that's still assembled using the `Deref` trait just as now. But, a wider set of locations is searched for methods with those receiver types.
250+
251+
For instance, `Weak<T>` implements `Receiver` but not `Deref`. Imagine you have `let t: Weak<SomeStruct> = /* obtain */; t.some_method();`. We will now search `impl SomeStruct {}` blocks for an implementation of `fn some_method(self: Weak<SomeStruct>)`, `fn some_method(self: &Weak<SomeStruct>)`, etc. The possible self types are unchanged - they're still obtained by searching the `Deref` chain for `t` - but we'll look in more places for methods with those valid `self` types.
252+
253+
## Compiler changes: deshadowing
254+
[compiler-changes-deshadowing]: #compiler-changes-deshadowing
255+
256+
The major functional change to the compiler is described above, but a couple of extra adjustments are necessary to avoid future compatibility breaks by method shadowing.
257+
258+
Specifically, that page also states:
259+
260+
> If this results in multiple possible candidates, then it is an error, and the receiver must be converted to an appropriate receiver type to make the method call.
261+
262+
This changes. For smart pointer types which implement `Receiver` (such as `NonNull<T>`) the future addition of any method would become an incompatible change, because it would run the risk of this ambiguity if there were a method of the same name within `T`. So, if there are multiple candidates, and if one of those candidates is in a more "nested" level of receiver than the others (that is, further along the chain of `Receiver`), we will choose that candidate and warn instead of producing a fatal error.
263+
264+
Similarly,
265+
266+
> Note: the lookup is done for each type in order, which can occasionally lead to surprising results.
267+
268+
This changes too, for the same reason. We check for matching candidates for `T`, `&T` and `&mut T`, and again, if there's a candidate on an "inner" type (that, is, further along the chain of `Receiver`) we will choose that type in preference to less nested types and emit a warning.
269+
270+
(The current reference doesn't describe it, but the current algorithm also searches for method receivers of type `*const Self` and handles them explicitly in case the receiver type was `*mut Self`. We do not check for cases where a new `self: *mut Self` method on an outer type might shadow an existing `self: *const SomePtr<Self>` method on an inner type. Although this is a theoretical risk, such compatibility breaks should be easy to avoid because `self: *mut Self` are rare. It's not readily possible to apply the same de-shadowing approach to these, because we already intentionally shadow `*const::cast` with `*mut::cast`.)
240271

241272
## Object safety
242273

@@ -276,6 +307,11 @@ The existing branches in the compiler for "arbitrary self types" already emit ex
276307
}
277308
```
278309
We don't know a use-case for this. There are several cases where this can result in misleading diagnostics. (For instance, if such a method is called with an incorrect type (for example `smart_ptr.a::<&Foo>()` instead of `smart_ptr.a::<Foo>()`). We could attempt to find and fix all those cases. However, we feel that generic receiver types might risk subtle interactions with method resolutions and other parts of the language. We think it is a safer choice to generate an error on any declaration of a generic `self` type.
310+
- As noted in [#compiler-changes-deshadowing](the section about compiler changes for deshadowing) we will downgrade an existing error to a warning if there are multiple
311+
method candidates found, if one of those candidates is further along the chain of `Receiver`s than the others.
312+
- As also noted in [#compiler-changes-deshadowing](the section about compiler changes for deshadowing), we will produce a new warning if a method in an inner type is chosen
313+
in preference to a method in an outer type ("inner" = further along the `Receiver` chain) and the inner type is either `self: &T` or `self: &mut T` and we're choosing it
314+
in preference to `self: T` or `self: &T` in the outer type.
279315

280316
# Drawbacks
281317
[drawbacks]: #drawbacks
@@ -297,7 +333,44 @@ Rust standard library smart pointers are designed with this shadowing behavior i
297333

298334
Furthermore, the `Deref` trait itself [documents this possible compatibility hazard](https://doc.rust-lang.org/nightly/std/ops/trait.Deref.html#when-to-implement-deref-or-derefmut), and the Rust API Guidelines has [a guideline about avoiding inherent methods on smart pointers](https://rust-lang.github.io/api-guidelines/predictability.html#smart-pointers-do-not-add-inherent-methods-c-smart-ptr).
299335

300-
These method shadowing risks also apply to `Receiver`. This RFC does not make things worse for types that implement `Deref`, it only adds additional flexibility to the `self` parameter type for `T::m`.
336+
This RFC does not make things worse for types that implement `Deref`.
337+
338+
_However_, this RFC allow types to implement `Receiver`, and in fact does so for `NonNull<T>` and `Weak<T>`. `NonNull<T>` and `Weak<T>` were not designed with method shadowing concerns in mind. This would run the risk of breakage:
339+
340+
```rust
341+
struct Concrete;
342+
343+
impl Concrete {
344+
fn wardrobe(self: Weak<Self>) { }
345+
}
346+
347+
fn main() {
348+
let concrete: Weak<Concrete> = /* obtain */;
349+
concrete.wardrobe()
350+
}
351+
```
352+
353+
If Rust now adds `Weak::wardrobe(self)`, the above valid code would start to error.
354+
355+
The same would apply in this slightly different circumstance:
356+
357+
```rust
358+
struct Concrete;
359+
360+
impl Concrete {
361+
fn wardrobe(self: &Weak<Self>) { } // this is now a reference
362+
}
363+
364+
fn main() {
365+
let concrete: Weak<Concrete> = /* obtain */;
366+
concrete.wardrobe()
367+
}
368+
```
369+
370+
If Rust added `Weak::wardrobe(&self)` we would start to produce an error here. If Rust added `Weak::wardrobe(self)` then it would be
371+
even worse - code would start to call `Weak::wardrobe` where it had previously called `Concrete::wardrobe`.
372+
373+
The [#compiler-changes-deshadowing](deshadowing section of the compiler changes, above), describes how we avoid this. The compiler will take pains to identify any such ambiguities. If it finds them, it will warn of the situation and then choose the innermost method (in the example above, always `Concrete::wardrobe`).
301374

302375
# Rationale and alternatives
303376
[rationale-and-alternatives]: #rationale-and-alternatives
@@ -367,10 +440,6 @@ This RFC proposes to implement `Receiver` for `*mut T` and `*const T` within the
367440

368441
We prefer the option of specifying behavior in the library using the normal trait, though it's a compatibility break for users of Rust who don't adopt the `core` crate (including compiler tests).
369442

370-
## Implement for `Weak` and `NonNull`
371-
372-
`Weak<T>` and `NonNull<T>` were not supported by the prior unstable arbitrary self types support, but they share the property that it may be desirable to implement method calls to `T` using them as self types. Unfortunately they also share the property that these types have many Rust methods using `self`, `&self` or `&mut self`. If we added to the set of Rust methods in future, we'd [shadow any such method calls](#method-shadowing). We can't implement `Receiver` for these types unless we come up with a policy that all subsequent additions to these types would instead be associated functions. That would make the future APIs for these types a confusing mismash of methods and associated functions, and the extra user complexity doesn't seem merited.
373-
374443
## Not do it
375444
[not-do-it]: #not-do-it
376445

0 commit comments

Comments
 (0)