-
Notifications
You must be signed in to change notification settings - Fork 13.9k
Description
arbitrary_self_types (v1 RFC, v2 amending RFC) is currently being considered for stabilization in #135881. This feature, designed to support methods which take self by an expanded set of user-defined types like self: SmartPointer<T>, self: CppRef<T>, makes several changes to method resolution.
How method lookup works today
Today, we search for methods on a type T in this order (rustc dev guide, reference):
- Methods for type
Twith aSelftype ofself,&self,&mut self - Deref: while
TisDeref,NewT = <T as Deref>::Target, considerT,&T,&mut T - Unsizing: once
Tis no longerDeref, ifT == [A; N], consider[A],&[A],&mut [A]
For each considered type, preference is given for inherent methods over extension methods (trait methods).
How method lookup works under arbitrary_self_types
arbitrary_self_types v2 changes this look up (RFC. reference PR):
- The first and most obvious change is that it uses the
Receivertrait rather than theDereftrait for looking up candidate receiver types. This allows for method receivers likefn takes_cppref(self: CppRef<T>)whereCppRefis some type that cannot implementDeref<Target = T>. - However, the more significant change is that
arbitrary_self_typesallows for methods to be defined forimpl MyType { fn foo(self: PtrLike<MyType>) { ... } }for customPtrliketypes.
Other related outstanding features
- The
arbitrary_self_types_pointersfeature considers*const Tmethods for each candidate receiver type which is a*mut T. - The
pin_ergonomicsfeature as documented here consider "method[s] with aPinthat's reborrowed" (note: I, cramertj@, don't actually understand at this time how this changes the candidate or method set).
Why custom autoref
I created this issue because I believe that most uses of arbitrary_self_types that I'm aware of actually want custom autoref behavior (the exception is external std-likes e.g. RFL's Arc). That is, rather than extending the candidate set of receiver types, I believe they may instead/also want to modify the per-candidate set of Self types searched. I want to ensure that the parts of arbitrary_self_types being stabilized do not hamper our ability to do add autoref behavior (at least for Pin, if not for custom types).
For example:
struct MyType { ... }
impl MyType {
fn foo(self: CppRef<Self>) { ... }
}
let x = MyType { ... };
x.foo(); // ERRORDoing lookup for foo on MyType, we look for methods taking self: MyType, self: &MyType, and self: &mut MyType, see that there's no receiver impl or unsizing to follow, and give up. What should happen is that CppRef should behave as & does (as it is strictly less powerful than & and can be created from T "for free").
Note that this is a per-candidate type behavior, as we'd want Box::new(MyType{ ... }).foo() to work as well. That is, method resolution for foo on Box<MyType> should look for foo as a by-value, by-ref, by-cppref, by-mutref, and by-cppmutref method on candidate types Box<MyType> and then MyType.
self: Box<MyType>,self: &Box<MyType>,self: CppRef<Box<MyType>>,self: &mut Box<MyType>,self: CppMutRef<Box<MyType>>self: MyType,self: &MyType,self: CppRef<MyType><<< this one is found and selected
Similarly, we could imagine doing the same thing in order to support by-Pin methods on types which are Unpin (Unpin types can convert from Self to Pin<&Self>/Pin<&mut Self> "for free"):
struct MyType { ... }
impl Future for MyType { fn poll(self: Pin<&mut Self>, ... ) -> ... { ... } }
let x = MyType { ... };
x.poll(..) // ERROR today, we'd like this to work, maybe with something like
impl<T: ?Sized + Unpin> AutoRef<Pin<&T>> for T { ... }
impl<T: ?Sized + Unpin> AutoRefMut<Pin<&mut T>> for T { ... }Similarly, we'd also love for Pin<&mut Self> methods to be callable on Pin<Box<Self>> receiver types, which can create a Pin<&mut Self> "for free", maybe with an impl like:
impl<T: ?Sized> AutoRef<Pin<&T> for Pin<Box<T>> { ... }
impl<T: ?Sized> AutoRefMut<Pin<&mut T>> for Pin<Box<T>> { ... }Other examples of autoref-like non-Receivers that we'd like to consider in method resolution:
- @veluca93 mentioned having image types that would like to autoref into mutable image views.
- @sarah-quinones brought up the
faerMatMuttype which is a mutable view over a matrix. Ideally, methods which work on aMatMutwould be callable on a local of typeMat.
Is this even a thing we can do?
Maybe not. Making method resolution search a (# of autoref types for Self) * (Receiver/Deref impls for Self + unsize) number of types when looking for a method seems expensive, but I don't have a good idea of how expensive.
However, I think this is well-motivated at least for Pin, and possibly some other select set of types.
Future compatibility issues
At this point this issue is mostly FUD, unfortunately-- I don't have a specific concern besides "method resolution is getting more complicated but maybe in the wrong way." Pin is already stable as a self type, so any extensions we make to support at least Pin-autoref have to be done in a backwards compatible way. Therefore, it may be the case that we're not making anything worse for ourselves by allowing other non-stdlib types to have this ability.
The arbitrary_self_types v2 RFC does say that arbitrary_self_types makes it so that "a wider set of locations is searched for methods with those receiver types." I haven't completely understood this-- it seems like the set of places we have to look today for a potential self: Arc<Self> method is the same set of locations we'd have to look for a self: NonStdArc<Self> (that is, we have to search both the impls of ...Arc and Self as well as any <Self as Receiver>::Target).
Am I (@cramertj) missing something? Are we committing to other method resolution complexities by stabilizing arbitrary_self_types that will make it harder to add autoref support for Pin or CppRef?