Trait::function should call the exact implementation of a function or be disabled as discouraging


#1

May be it was discussed somewhere, but I expect the case below (calling MyTrait::do_smth_and_make_11(self)) not to go into any recursion, but rather be a compiler error or to call the exact implementation from a trait (I prefer the latter variant, but you decide):

[ playpen ]

pub trait MyTrait {

    fn make_11(&mut self) -> &mut Self;

    fn do_smth_and_make_11(&mut self) -> &mut Self { 
        // a lot of code
        self.make_11()
    }
    
    // adding methods here (trait is public) will 
    // break `MyTrait` usage intentions

}

struct MyStruct { value: int }

impl MyStruct { }

impl MyTrait for MyStruct {

    fn make_11(&mut self) -> &mut MyStruct {
        self.value = 11;
        self
    }
    
    fn do_smth_and_make_11(&mut self) -> &mut MyStruct {
        self.value = 10;
        // ---> goes into recursion here <---
        MyTrait::do_smth_and_make_11(self)
    }
    
}

fn main() {

    let mut test = MyStruct { value: 5 };
    test.do_smth_and_make_11();
    println!("{}", test.value);

}

#2

To be clear: by “exact implementation” you mean the default implementation in the trait definition?


#3

Yes. Since we refer to it by its name.


#4

That doesn’t make sense because traits don’t have methods, they specify interfaces.

When you say:

trait Test {
  fn test(&self) {
    println!("it works!");
  }
}
struct X;
impl Test for X { };

What you’re actually saying is “Implement Test on X filling in any default implementations that I don’t specify.” Basically, Test::test(x) (where x : X) means “lookup the method test in X’s implementation of the Test trait and call it on x.” This method could have either been manually defined by the programmer or “filled in” by the compiler.

To further illustrate this point, the following doesn’t work:

trait Test {
    fn print() {
        println!("test");
    }
}

fn main() {
    Test::print();
}

Why? Because Test describes an interface and doesn’t really exist. If you want this to work, you have to add:

impl Test for () {}

As a matter of fact, you don’t even have to implement it on (), it just has to be implemented on something:

struct Stuff;
impl Test for Stuff {}

TL;DR, traits don’t have methods; they have method signatures and default implementations.


#5

I think default implementation can be referred as an “abstract method”.

If this method needs self pointer to be passed inside, then user can’t call it without having a corresponding trait implementor—so I see no conflicts with “interface” or “abstract class” case. If it’s a “static” method of a trait, then I suppose it’s just a free function in some namespace, so it may freely be called.

Though, on the other hand if a trait is commonly a verb (“ability”), then probably it can not act as a true namespace (a subject), but then why it may have static methods?

Anyway, visually, when user like me reads a syntax like Trait::method, he expects some concrete method to be called, since it’s a quite precise path, similar to ones from use section. …But then user gets some unexpected SuggestedImplementor::method called instead.

So, if “call exact method” approach is not right, then I suppose this ability should be disabled completely, not to confuse end-users.


#6

Static Trait Methods

It can’t. A trait can only describe static methods on implementations of the trait. Traits do not have methods; traits have method signatures and can optionally provide “suggested implementations”. To quote myself:

To further illustrate this point, the following doesn’t work:

trait Test {
    fn print() {
        println!("test");
    }
}

fn main() {
    Test::print();
}

Why? Because Test describes an interface and doesn’t really exist. If you want this to work, you have to add:

impl Test for () {}

As a matter of fact, you don’t even have to implement it on (), it just has to be implemented on something:

struct Stuff;
impl Test for Stuff {}

TL;DR, traits don’t have methods; they have method signatures and default implementations.

The method exists on the data structure. Test::print is just the name of the method (see below).

Method Naming

Trait::method is absolutely necessary because it’s the fully qualified name of the method and rustc can’t always figure it out.

Given:

trait TraitA {
    fn do_it(&self) {
        println!("TraitA")
    }
}

trait TraitB {
    fn do_it(&self) {
        println!("TraitA")
    }
}

struct Data;

impl TraitA for Data {}
impl TraitB for Data {}

The following obviously doesn’t work because rust doesn’t know which do_it method to call:

fn main() {
    let d = Data;
    d.do_it();
}

In this case, you have to use the fully qualified function name:

fn main() {
    let d = Data;
    TraitA::do_it(&d);
}


#7

Thank you, now I get it. I forgot about the existence of overlapping methods in Traits.

But then it’s a syntax question for me, still, since :: commonly used in current versions of rust to describe exact paths and reads like that to me, while this case you describe is… may be closer to… typecast?

Wouldn’t something like (d as TraitA).do_it() or (TraitA)(d).do_it() or TraitA(d).do_it() or whatever without :: and (&d) do better in this case? Or was it discussed somewhere already?


#8

It is actually part of the UFCS RFC which has been slowly landing recently. You will be able to do <Type as Trait>::method()


#9

I wish the language could have an option to simplify this out: just disallow traits with the same method name+signature from being implemented on the same type; Require library designers to collaborate on consistent method names, when their domains overlap. (I would hope traits would still allow methods of different signatures to work, e.g. the way multiparam type classes now give decent overloading, even if one trait can’t have overloads;)

This would allow closer translation to & from C++ methods. (you could always roll a C++ class that gathers trait implementations, or get further translating a C++ class into trait impls),and might be able to link identically mangled functions… and it would simplify navigation. (“where is this method”, not “what trait is this method in, where is the trait”… a double indirection in navigation)


#10

I think I do agree with this, since the language spec tells it has no method overloading, and having the similarly-named methods in different traits is kinda creepy way of actually doing that, under cover.

An ability to reach some “default implementation” with a path and pass there an implementor instance, would be a nice feature for me, though.