Problem using methods defined in extended trait

Hello,
I'm exploring Rust and the following code is an experiment that doesn't yield the results I believe are easily understandable (you should easily understand the problems I encounter in the code comments).

In short: is my problem, my perplexity, due to a lack of understanding of the syntax (which is very likely) or, as I suspect, due to a lack of understanding of what happens behind the scenes (which is practically certain)?

Does anyone feel like answering my questions?

Thanks anyway.

And if clarification is needed, please let me know


struct MyStruct {
    f1: i32, 
}

impl MyStruct {
    fn new(p1: i32) -> MyStruct{
        println!("Inside MyStruct::new({})", p1);
        MyStruct{f1: p1,}
    }

    fn show(&self){
        println!("Inside MyStruct::show: {} ObjAddr = {:p}", self.f1, self);
    }
}
// ----------------------------------------------------------------
trait MyTrait00<T> {
    fn meth_01_tr00(&self, t:T) -> T {
        println!("Default implementation for MyTrait00<T>::meth_01_tr00 - ObjAddr = {:p}", self);
        t
    }

    fn meth_02_tr00(&self){
        println!("Default implementation for MyTrait00<T>::meth_02_tr00 - ObjAddr = {:p}", self);
    }  
}

impl<T> MyTrait00<T> for MyStruct {

}


trait ExtendMyTrait00<T,U>:MyTrait00<T> {
    fn meth_01_extr00(&self,u:U) -> U {  
        println!("Default implementation for ExtendMyTrait00::meth_01_extr00 - ObjAddr = {:p}", self);
        u
    }
}



impl<T,U> ExtendMyTrait00<T,U> for MyStruct {

}

fn main() {
    {
        let my_struct_01 = MyStruct::new(111);
        //  classic sintax
        my_struct_01.show();
        //  alternative/convoluted sintax
        MyStruct::show(&my_struct_01);
        

        // 
        //  using methods of MyTrait00:     ------------------------
        //
        //          Classic sintax
        let _x = my_struct_01.meth_01_tr00('K');
        //let _y = my_struct_01.meth_02_tr00();         //  ERROR: type annotations needed.
                                                        //  I'm not able to use .meth_02_tr00()

        //          alternative/convoluted sintax
        let _x = my_struct_01.meth_01_tr00(55.5);                           // convoluted sintax is not necessary
        let _y = <MyStruct as MyTrait00<()>>::meth_02_tr00(&my_struct_01);  //  Now I'm able to use 
                                                                                      // .meth_02_tr00()
        
    
        //  using methods of ExtendMyTrait00  ----------------------

        //          Classic sintax
        //let _x = my_struct_01.meth_01_extr00(5);            //    ERROR: type annotations needed;
                                                              //    I'm not able to use .meth_01_extr00
        //let _x= my_struct_01.meth_01_extr00<(),i32>(u);     //    ERROR: expected semicolon
                                                              //    I'm not able to use .meth_01_extr00
        
        //          alternative/convoluted sintax
        let _x  = <MyStruct as MyTrait00<char>>::meth_01_tr00(&my_struct_01, 'Q'); 
        let _y    = <MyStruct as MyTrait00<()>>::meth_02_tr00(&my_struct_01);

        let _z   = <MyStruct as ExtendMyTrait00<(), i32>>::meth_01_extr00(&my_struct_01, 5);    //  Now .meth_01_extr00 is visible.
        //let _y = <MyStruct as ExtendMyTrait00<(), i32>>::meth_01_tr00(&my_struct_01, 5);      //  Why is 'inherited' meth_01_tr00  
                                                                                                //  not visible from the trait ExtendMyTrai00?"
                                                                                                
        //let _w = <MyStruct as ExtendMyTrait00<i32,f64>>::meth_02_tr00(&my_struct_01);         //  Why is 'inherited' meth_02_tr00 
                                                                                                //  not visible from the trait ExtendMyTrai00?" 
                                                                                                

        //          trait objects sintax
        let my_trait_object_01: &dyn MyTrait00<String> = &my_struct_01;
        let _x = my_trait_object_01.meth_01_tr00("Fabio".to_string());
        let _y     = my_trait_object_01.meth_02_tr00();

        let my_trait_object_02: &dyn ExtendMyTrait00<f64,char> = &my_struct_01;      // Specifico i 2 type parameter
        let _x    = my_trait_object_02.meth_01_tr00(999.9);          //  Now the 'inherited' method is visible.
        let _y     = my_trait_object_02.meth_02_tr00();               //  Now the 'inherited' method is visible.
        let _z   = my_trait_object_02.meth_01_extr00('K');

    }
}

This is because MyTrait00<T> has a type parameter, so you have to provide a specific T in order to use the trait at all, and each method may have a different behavior depending on what T is. If this is not what you want, then you should declare the trait with a type parameter on only the method that needs it, not on the trait:

trait MyTrait00 {
    fn meth_01_tr00<T>(&self, t: T) -> T { ... }

    fn meth_02_tr00(&self) { ... }  
}

Putting generic parameters on a trait should be done only when necessary because it's a significant increase in complexity. It makes sense when the operations the trait describe apply to two types that can both vary (Self and T) instead of just one type Self and other inputs that are predictable given Self.


< here means the less-than operator, which is not what you wanted and results in a syntax error later because the comma doesn't make syntax afterward. To use generic parameters on a method or function call, you need the "turbofish" syntax ::<parameters...>:

let _x = my_struct_01.meth_01_extr00::<(),i32>(u);
//                                  ^^ add these colons

This is not a difference in semantics: it is just a special syntax for the generic-parameter-list "<" symbol that you use when you're writing in an expression position instead of a type position.

First of all, thank you for your response.
However, I must say that your suggestion to use

let _x = my_struct_01.meth_01_extr00::<(),i32>(777);

generates the following error:

method takes 0 generic arguments but 2 generic arguments were supplied
expected 0 generic arguments

Ah, that's because (in your original code) the type parameters are defined on the trait, and not on the method. You cannot specify traits' parameters explicitly in a method call — you have to either let them be inferred if possible, or use function call syntax and name the trait, with one of these syntaxes:

ExtendMyTrait00::<(),i32>::meth_01_extr00(&my_struct_01, 777)
// or
<MyStruct as ExtendMyTrait00<(),i32>>::meth_01_extr00(&my_struct_01, 777)

But if you arrange so that the type parameters are on the methods and not on the traits, as I discussed previously, then you can specify them in the .method::<types>() position.

1 Like

I suppose that this is the key point, thank you.

But then I take the opportunity to return to another of the problematic points for me in the example I proposed.

Why, starting from the trait ExtendMyTrait00, "I cannot reach" the methods inherited from the trait MyTrait00?
With an example:
why doesn't IntelliSense also show me meth_01_tr00() and meth_02_tr00() and return an error if I write

let _y = <MyStruct as ExtendMyTrait00<(), i32>>::meth_01_tr00(&my_struct_01, 5); //ERROR!

instead, why can I "reach them" using trait objects as in the following instructions:

let my_trait_object_02: &dyn ExtendMyTrait00<f64, char> = &my_struct_01;
let _x = my_trait_object_02.meth_01_tr00(999.9); // Now the 'inherited' method is visible.
let _y = my_trait_object_02.meth_02_tr00(); // Now the 'inherited' method is visible.
let _z = my_trait_object_02.meth_01_extr00('K');

The <Type as Trait>::function qualified path syntax is intended as the last resort for disambiguation; it lets you specify exactly which trait is providing the function you want to call, without any other traits potentially getting in the way. You can't call meth_01_tr00 using it because meth_01_tr00 is not one of the associated functions of ExtendMyTrait00.

Rust does not have inheritance as commonly understood. When you write trait ExtendMyTrait00<T,U>: MyTrait00<T>, that does not mean ExtendMyTrait00 inherits from MyTrait00. Rather, it means a logical implication: every type which implements ExtendMyTrait00 must also implement MyTrait00. This doesn't affect what the associated functions of ExtendMyTrait00 are.

Additionally, if

let _y = <MyStruct as ExtendMyTrait00<(), i32>>::meth_01_tr00(&my_struct_01, 5);

worked, that would imply that there are distinct methods

<MyStruct as ExtendMyTrait00<(), i32>>::meth_01_tr00
<MyStruct as ExtendMyTrait00<f64, i32>>::meth_01_tr00
<MyStruct as ExtendMyTrait00<bool, i32>>::meth_01_tr00
// ...

but there is not; there is only

<MyStruct as MyTrait00<i32>>::meth_01_tr00

On top of that, you can define a meth_01_tr00 method in ExtendMyTrait00. In that case,

  • The distinct methods would exist (in addition to MyTrait00::meth_01_tr00), and
  • They can do different things than MyTrait00::meth_01_tr00, so
  • You must disambiguate which to call

Another key point.
Thank you.

I understand that you are saying "this is not possible, but if it were then this would imply XXX."
I would like to understand why this implication holds.

Because trait methods are parameterized by all of

  • any generics they directly have
  • any generics the trait has
  • the implementing type