In Rust, I tend to have design issues when it comes to writing modules with traits. I'm not always sure whether I want:
pub trait Foo {
fn foo(&self) -> bool;
fn bar(&self) {
if self.foo() {
// Do something!
} else {
// Do something else!
}
}
}
Or
pub trait Foo {
fn foo(&self) -> bool;
}
pub fn bar<T>(fooer: &T) where T: Foo {
if fooer.foo() {
// Do something!
} else {
// Do something else!
}
}
(Of course, real examples are likely to have more elaborate traits or function signatures)
While issues of design are beyond the scope of Stack Overflow, I'm not entirely sure I even understand the meaningful, objective differences between the two, and I don't feel like browsing the standard library has shed much light. It seems like Rust's standard library prefers to use variable.method()
as opposed to mod::function(&variable)
in most cases. However, that still doesn't really answer the question since that's just a style guide argument rather than being based on objective knowledge about the difference.
Other than the obvious syntactic difference, what are the main functional differences between a default trait method and a module-level parameterized function? One big question I have is: does the default trait method monomorphize to use static dispatch, or does if it take self
as if it were a trait object?
The only difference I'm seeing off the top of my head is that an impl
of the trait may elect to override the default method implementation, hopefully/presumably providing an implementation that fulfills the same contract, while I'm guaranteed that the mod::function
implementation always runs the exact same code no matter what (for better or worse). Is there anything else? Does the answer change if associated types or extension traits are involved?
A trait tells the Rust compiler about functionality a particular type has and can share with other types. Traits are an abstract definition of shared behavior amongst different types. So, we can say that traits are to Rust what interfaces are to Java or abstract classes are to C++.
To implement a trait, declare an impl block for the type you want to implement the trait for. The syntax is impl <trait> for <type> . You'll need to implement all the methods that don't have default implementations. If your implementation is incomplete, your trusty friend the compiler will let you know.
Rust is not an object oriented language. And traits are not exactly interfaces.
The impl keyword is primarily used to define implementations on types. Inherent implementations are standalone, while trait implementations are used to implement traits for types, or other traits. Functions and consts can both be defined in an implementation.
You actually answered the question yourself, congratulations!
Since Rust only has principled overloading/overriding via traits, the essential semantic difference is that a trait method
can be overridden, and thus customized, while a free function cannot.
Technically, both Trait::func(&self)
and mod::func<T: Trait>(&T)
are monophormized while mod::func(&Trait)
is not (and thus will incur the slight overhead of virtual calls).
Also, there is a slight memory overhead to Trait::func(&self)
: one more entry in the virtual table. It's probably unnoticeable.
In fine, the choice is generally a judgement call. Whether you open the door to customization or not is your choice.
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