Skip to content

Ensure arbitrary_self_types method resolution is forward-compatible with custom autoref #136987

Closed
@cramertj

Description

@cramertj

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 T with a Self type of self, &self, &mut self
  • Deref: while T is Deref, NewT = <T as Deref>::Target, consider T, &T, &mut T
  • Unsizing: once T is no longer Deref, if T == [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 Receiver trait rather than the Deref trait for looking up candidate receiver types. This allows for method receivers like fn takes_cppref(self: CppRef<T>) where CppRef is some type that cannot implement Deref<Target = T>.
  • However, the more significant change is that arbitrary_self_types allows for methods to be defined for impl MyType { fn foo(self: PtrLike<MyType>) { ... } } for custom Ptrlike types.

Other related outstanding features

  • The arbitrary_self_types_pointers feature considers *const T methods for each candidate receiver type which is a *mut T.
  • The pin_ergonomics feature as documented here consider "method[s] with a Pin that'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(); // ERROR

Doing 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:

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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-discussionCategory: Discussion or questions that doesn't represent real issues.F-arbitrary_self_types`#![feature(arbitrary_self_types)]`T-langRelevant to the language teamT-typesRelevant to the types team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions