Home > other >  Why does calling a trait method not call the inherent method of the same name when used generically?
Why does calling a trait method not call the inherent method of the same name when used generically?

Time:09-30

I'm new to Rust and I have a strange behavior in a generic function and some questions about it. I understand the result but I don't know if it is normal or not:

trait A {
    fn a() {
        println!("Oh Noooo...");
    }
    fn b();
}

struct SA {}

impl SA {
    fn a() {
        println!("sa");
    }

    fn b() {
        println!("b");
    }
}

impl A for SA {
    fn b() {
        println!("b2");
    }
}

fn foo<T>() where T: A {
    T::a();
    // <T as T>::a(); // -> Generate an error a not in T
}

fn bar<T>() where T: A {
    T::b();
    // <T as T>::b(); // -> Generate an error b not in T
}

fn main() {
    //<SA as SA>::a(); // -> Generate an error not found in SA
    SA::a();
    foo::<SA>();
    SA::b();
    bar::<SA>();
}
sa
Oh Noooo...
b
b2

Link to the playground

My problem is about the "Oh Noooo...". I know that the code is not conventional the function a() is not in the impl A for SA but why, in the generic function, is is not the a() in SA which is called? I thought the where clause was only a constraint on the fact that my struct implement a trait. It seems it is like the T is converted in a dyn A, is that the case?

Why is there an error when I want to force the call with <T as T> when the where constraint ensures the a existence?

My problem is in a more complex situation and the function a() is generated by an attribute macro on a function in my block impl SA and I don't know how to generate the function outside the impl block and if it is possible. Is the only solution to implement my macro for the whole impl block?

CodePudding user response:

I know that the code is not conventional the function a is not in the impl A for SA but why, in the generic function, it is not the a in SA which is called.

The resolution of names in a generic function (foo) is not based on the concrete type that the function is called with, but only the types and traits at the definition. When the compiler processes

fn foo<T>() where T: A {
    T::a();
}

the only information it uses to look up a in T is that T implements A. Thus, it becomes equivalent to A::a() or <T as A>::a(), invoking the trait method.

This is not just an arbitrary choice, but an important part of Rust's design — that a generic function works exactly the same regardless of any naming collisions or other incidental details of the type it's used with. To see how we could get an error if Rust worked differently, consider this program:

trait A {
    fn a(x: i32);
}

struct SA {}

impl SA {
    fn a(x: &str) {}
}

fn foo<T>() where T: A {
    T::a("hello world");
}

The trait function A::a takes a &str, but the inherent function SA::a takes an i32. If name resolution were to decide that inherent functions should be chosen if available, then foo::<SA>() would have a type error even though the generic function definition by itself is okay. This is bad because it means that generic functions could be made to fail simply because the type has a certain associated function name. And, it would be a “post-monomorphization error” — the generic function's definition could not be known to be valid (type-checked etc.) except in context of each usage of it. That would produce hard-to-debug problems and also slow down compilation since more checking is required.


To avoid this problem, you should think of inherent associated functions as being essentially in a different namespace from trait associated functions. You cannot make the inherent function be called from a generic function simply by giving it a matching name — generic functions only call trait functions (when a type variable is involved; of course, generic functions can call inherent functions of associated types).

I thought the where was only a constraint on the fact that my struct implement a trait.

It is, but the thing is that your generic function will never be able to call inherent SA::a() unless it mentions that concrete type explicitly. It's not that the where clause is hiding SA::a(), but that SA::a() cannot ever be relevant to T at all.

  • Related