If I have a trait, and a function that accepts a generic type constrained to that type, everything works fine. If I try to pass in a reference to that type, I get a compilation error.
trait Trait {
fn hello(&self) -> u32;
}
struct Struct(u32);
impl Trait for Struct {
fn hello(&self) -> u32 {
self.0
}
}
fn runner<T: Trait>(t: T) {
println!("{}", t.hello())
}
fn main() {
let s = Struct(42);
// Works
runner(s);
// Doesn't work
runner(&s);
}
error[E0277]: the trait bound `&Struct: Trait` is not satisfied
--> src/main.rs:24:5
|
24 | runner(&s);
| ^^^^^^ the trait `Trait` is not implemented for `&Struct`
|
= help: the following implementations were found:
<Struct as Trait>
note: required by `runner`
--> src/main.rs:13:1
|
13 | fn runner<T: Trait>(t: T) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^
I can fix the issue by implementing the trait for any reference to a type that implements the trait:
impl<'a, T> Trait for &'a T
where
T: Trait,
{
fn hello(&self) -> u32 {
(*self).hello()
}
}
The piece of information that I'm missing is when shouldn't I implement this? Asked another way, why doesn't the compiler automatically implement this for me? Since it currently doesn't, I assume there must be cases where having this implementation would be disadvantageous.
We can use traits as function parameters to allow the function to accept any type that can do x , where x is some behavior defined by a trait. We can also use trait bounds to refine and restrict generics, such as by saying we accept any type T that implements a specified trait.
A trait in Rust is a group of methods that are defined for a particular type. Traits are an abstract definition of shared behavior amongst different types. So, in a way, traits are to Rust what interfaces are to Java or abstract classes are to C++. A trait method is able to access other methods within that trait.
An implementation is an item that associates items with an implementing type. Implementations are defined with the keyword impl and contain functions that belong to an instance of the type that is being implemented or to the type statically. There are two types of implementations: inherent implementations.
What are blanket implementations? Blanket implementations leverage Rust's ability to use generic parameters. They can be used to define shared behavior using traits. This is a great way to remove redundancy in code by reducing the need to repeat the code for different types with similar functionality.
when shouldn't I implement this? Asked another way, why doesn't the compiler automatically implement this for me? Since it currently doesn't, I assume there must be cases where having this implementation would be disadvantageous.
As an example, the Default
trait immediately came to mind.
pub trait Default {
fn default() -> Self;
}
I could implement it for T
, but there is no way to automatically implement it for &T
.
The particular trait you are writing here only takes self
by reference, and that is the only reason it is possible to write the additional implementation you did.
For this reason, taking the parameter to runner()
by value is probably undesirable; you should instead be taking it by reference. This guideline can apply generally: if it is possible to implement the trait for a reference then rather than wondering “should I implement it?” you should wonder “why would I implement it?” for the only cases where you would use it should probably be altered to take the object by reference in the first place.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With