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:
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.
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.
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>::functionqualified 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 ExtendMyTrait00must also implementMyTrait00. This doesn't affect what the associated functions of ExtendMyTrait00 are.
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
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.