This is now covered in the second edition of The Rust Programming Language . However, let's go a little deeper.
Let's start with a simpler example.
When is it appropriate to use the trait method?
There are several ways to provide late binding:
trait MyTrait { fn hello_word(&self) -> String; }
Or:
struct MyTrait<T> { t: T, hello_world: fn(&T) -> String, } impl<T> MyTrait<T> { fn new(t: T, hello_world: fn(&T) -> String) -> MyTrait<T>; fn hello_world(&self) -> String { (self.hello_world)(self.t) } }
Despite any implementation / performance strategy, both of the above snippets allow the user to dynamically indicate how hello_world should behave.
The only difference (semantically) is that the trait implementation ensures that for a given type of T that implements trait , hello_world will always have the same behavior, while the struct implementation allows you to have different behavior as a basis.
Whether it is appropriate to use the method depends on the use case!
When is it appropriate to use a related type?
Like the trait methods described above, the associated type is a late-binding form (although this happens during compilation), allowing the trait user to specify for the given instance which type to replace. This is not the only way (so the question is):
trait MyTrait { type Return; fn hello_world(&self) -> Self::Return; }
Or:
trait MyTrait<Return> { fn hello_world(&Self) -> Return; }
Equivalent to late binding of the above methods:
- the first ensures that for a given
Self there is one Return associated - instead, the second allows you to implement
MyTrait for Self for multiple MyTrait
Which form is more appropriate depends on whether it makes sense to apply uniqueness or not. For example:
Deref uses a bound type because without uniqueness the compiler would go crazy during outputAdd uses a linked type because its author thought that given two arguments there would be a boolean return type
As you can see, while Deref is an obvious use case (technical limitation), the Add case is less clear: it might be advisable for i32 + i32 give either i32 or Complex<i32> depending on the context? Nevertheless, the author used his judgment and decided that overloading of the return type was not necessary for additions.
My personal position is that there is no right answer. However, in addition to the unicity argument, I would mention that related types simplify the use of a trait, since they reduce the number of parameters that must be specified, so if the flexibility of using a regular attribute parameter is not obvious, I suggest starting with a related type .