Rust traits are better interfaces
Cătălin August 23, 2023 [Rust] #TraitsIntro
Hi folks! Today we'll be comparing how interface polymorphism is handled in Rust and Java.
First, a short primer - Java is an OO programming language, while Rust is multi-paradigm one. Rust gives you the option of organizing your code by OO principles if you want, but it doesn't force you into this.
Java interfaces act as contracts that define a set of method signatures that implementing classes must adhere to. A class can implement multiple interfaces, enabling multiple inheritance of behavior.
Rust doesn't have classes - it uses data types and associated functions or methods. A trait in Rust just defines behavior that a type can exhibit.
Check out the video
If you don't feel like reading:
Flexibility
Practically speaking, for basic usage, they function very similarly. You need to declare that a data structure (or class for Java) implements a trait and interface, and implementing multiple traits or interfaces is allowed in both cases.
There's one slight difference that gives Rust an edge in flexibility - In Rust, you can declare the implementing relationship anywhere.
In Java, I have to declare inside the class definition that it implements an interface. In this way, the relationship is stored inside the class itself.
In Rust, I'm free to choose where the struct, trait and implementation statement reside. I can have them in the same file one after the other or totally separate files if I wish.
Moreover, I can implement an external trait for a type in my codebase, which is possible in Java as well, but I can also implement a trait in my codebase for an external type, which makes plugging in external components into my API trivial. I can also implement external traits for external types, but this requires a special pattern.
use SomeStruct;
Derives
Too lazy to write implementations? Rust allows for deriving automatic implementations for certain types. Deriving Eq makes it easy to compare structs.
let p1 = Person
let p2 = Person
p1 == p2
Deriving Debug allows for an easy way to print a struct.
let p1 = Person ;
print!;
:? is the debug format string.
Associated types
Another aspect that gives Rust traits more power is the associated type, where we can force implementors to define this type.
In the example above from the standard library, implementing the Iterator trait requires defining the Item type. Here we're defining it as a Thing in a custom collection of Things.
You can also use the associated type in the argument of the function
Another detail you may have picked up on is the different signatures that can be used in the trait functions:
In Rust, it's immediately visible if the trait function has mutable or immutable access to the member data of the struct or if it has no access to this. In Java, even if the interface is more coupled to the class, you don't get this kind of visibility. You'd have to dig into the implementation in order to check this.
Dispatch
One other important difference that is not easily visible is method dispatch. The two languages have almost diametrically opposed approaches.
Rust will use static method dispatch for traits (The appropriate method implementation is baked into the compiled binary directly) by default. The programmer can opt into dynamic dispatch by using trait objects. These are heap-allocated boxes that can hold a struct that implements the trait in a situation where the compiler cannot figure out exactly what type it is.
Java takes the exact opposite approach, where most calls will use dynamic method dispatch by default, except in some cases where the compiler can figure out that only a certain specific implementation is used.
The Java approach sacrifices some performance and explicitness for convenience, while Rust defaults to performance and transparency around the type of dispatch being used. This increased visibility will come at the cost of needing to type a bit more boilerplate.
Overall, I'd say the approach Rust takes is favorable due to greatly increased flexibility at the cost of some new syntax to learn for the most part.