Looking for use cases of both using keyword `Self` and restricting to select inherent method somehow

Hello, I'm reviewing RFC #3913: Natural Method Disambiguation (Pre-RFC: Proposal: Syntactic Sugar for Disambiguating Conflicting Trait Methods), and I'm a bit doubtful of the RFC's reason for expr.Self::method(...) instead of expr.<Self>::method(...) (underline & i.e. mine):

In impl blocks, we can apply obj.Self to objects that do not have the type named Self in that block. obj.<Self> would look like we are trying to apply a method of one type to an object of another type even if they happen to be the same.

(i.e. the Self in expr.<Self>::method(...)currently proposed is expr.Self::method(...), as an indicator for "restrict to inherent methods", is easily confused with the Self type of the current impl block or trait block)

Unfortunately, I find it hard to find examples to test this statement, other than this example in the RFC. Therefore, I am looking for code samples that:

  1. has a means of calling an inherent method, where, without that means, the default semantics would be to call a trait method
  2. does the call to the inherent method in an impl block or trait block
  3. in that impl block or trait block, also uses the keyword type Self
  4. preferably, the value whose inherent method is being called, has a different type from the Self type of the impl block or trait block

The above translation of the semantics may be a bit inaccurate; what I'm ultimately looking for is code that could be rewritten using RFC's syntax to look like this:

impl ... {
    fn ... {
        let v = vec![Self::new(); 999];
        v.iter().for_each(|x| x.Self::method());
        // where x.method() would call SomeTrait::method
        // which is not what we want

        // Note: this fails criterion #4 but I can't think of
        // something that does for now
    }
}

And, as mentioned above, I'm also looking for opinions of whether that syntax should be expr.Self::method(...), or expr.<Self>::method(...), or something else.

Note: My main purpose for the topic is to collect code samples, so please include a working code sample!! You can add your opinions, but since my main purpose is to collect code samples, I won't show my approval/diasapproval.

1 Like

I don’t understand the quoted argument.

Note that currently <Self>::method is valid code and in Self’s case (but not in general) the same as Self::method(). This feature should (must?) support <Foo> paths anyway, so not supporting the "naked" format (where it’s not ambiguous) would be inconsistent.

Well, I just quoted the original text and added an "i.e." which is actually my understanding. You might want to leave an inline comment in the RFC PR.

I don't know what you're talking about. The two proposed syntaxes are expr.<Trait>::method(args) (TraitSelf) and expr.Self::method(args), as a whole, and I'm looking for code samples to discuss the latter. <Self>::method and Self::method(args) are not part of the RFC.

I think Self should always mean the Self type of the enclosing impl block, so neither obj.Self nor obj.<Self> make sense.

There are however two reasonable questions that come up in that discussion:

Frist, how do you bypass traits without using a submodule? It's extremely rare one does this, because rust picks the inherrent method anytime the type is known. It'll only come up when writing macro code or include!s that cannot know if they're being used when the type is known.

Now there is plenty of code like <Foo>::default() in the wild, so <Foo>:: cannot really ever mean "ignore the traits". Instead, you need something like <Foo as !>:: to say "ignore the traits." In this, <_>:: alone cannot say "ignore the traits" since _ could reference a type parameter. And <_>::default should exist in the wild.

It'd maybe make sense to have some UFCS like <_ as !>::(*obj), which uses _ because the macro might not know the type. As above <Self as !>::method(*obj)' could only mean Self` comes from some enclosing impl block.

It's clear that <T as !>:: must error whenever T winds up being a type parameter, so it matters that Self has a concrete type here. In particular, there are parametric questions, like Bar::<T>::new exists only within blocks where T: Foo:

impl<T: Foo> Trait<T> for Bar<T> {
    fn new() -> Self { .. }
}

Second, should . be availavble in such niceh situations? Imho no, there are big advantages in the . syntax saying "all name resultion uses exactly the standard simple-ish rules", but then going to existing UFCS for anything non-standard.

That said, one could imagine this being a lint, so obj.<_ as !>::method() should error or warn by default, but some #[allow(allow_complex_recievers)] makes this allowed for macro authors.

3 Likes

That's what I'm looking for. Can you give a code sample?

No, because I do not write many macros. There are few use cases for this feature outside macros that use "sloppy" non-trait polymorphism.

I've only really needed sloppy non-trait polymorphism when doing FFTs over complex extension fields, because then you want to try several different levels of precomputing the multipliers. It turnned out traits fit thius problem poorly. include! fit this problem better than macros though.

Anyways obj.<_ as !>::method() would always either throw an error or do exactly what obj.method does, so the only reason for this feature would be to throw that error. As I said, that's a reasonable feature in macros, but why would you ever do this outside macros?

At present, only reasons come to mind: You felt dong this made the code more readable, maybe because you disagree with some trait author's method name choices.

Now we have the opposite situations: You wish to call the trait method. You might do this for clarity or because you're worried about a supply chain attack. Afaik UFCS handles this cleanly now, but maybe if you wanted to do this lots then you'd spend the time.

I think there are better ways to handle the supply chain attack concern, like some cargo audit-upgrade that somehow observes and describes all the call structure. changes. We obverall have a defense in depth issue here though, so maybe.

Afaik this covers all use cases: sloppy non-trait polymorphism using macros of include!, code clarity when you disagree with other's naming choices, and maybe supply chain attack concerns.

1 Like

You've mentioned the use cases you know, do you know some libraries that involve them?