In Rust, a trait is a way to define shared behavior. It acts as a "contact" or "label" that you can attach to different structs.
Instead of caring about what struct is, a trait tells the Rust Compiler what a struct can do. So if multiple, completely unrelated structs need to perform the same kind of action, you can group them under a single trait.
Example
Let's make an analogy about USB's into actual Rust code.
A computer doesn't have a separate hole for Mouse, Keyboard, and Webcam. It just has a USB port, right?. So in Rust, we represent these concepts by creating a USBDevice trait.
Definition of Trait and Structs
First, we have to define the trait (the label we've already talked about) and some structs that have nothing in common internally:
// The trait (label)
trait UsbDevice {
fn connect(&self);
}
// First Struct
struct Mouse {
dpi: u32,
}
// Second Struct
struct Keyboard {
mechanical: bool,
}
Implementing the Trait
Then, we have to put our structs into the Trait. So we are proving to the compiler that both of these items can act as USB device:
impl UsbDevice for Mouse {
fn connect(&self) {
println!("Mouse Connecteeeeed! And ready to do some clicking");
}
}
impl UsbDevice for Keyboard {
fn connect(&self) {
println!("Keyboard connected! And ready to do some typingggg");
}
}
Using the Trait inside a function (POLYMOPHISM)
Here is where the "cool thing" happens. We write a function that represents the computer plugging something in. So instead of writing plug_in_mouse(device: Mouse) and plug_in_keyboard(device: Keyboard), we write only ONE function that accepts ANYTHING with the UsbDevice label.
// The "impl UsbDevice" syntax here means that it accepts ANY struct that has this trait!!!
fn plug_into_computer(device: &impl UsbDevice) {
device.connect();
}
fn main() {
let my_mouse = Mouse { dpi: 1600 };
let my_keyboard = Keyboard { mechanical: true };
plug_into_computer(&my_mouse);
plug_into_computer(&my_keyboard);
}
But why are Traits so important?
Here I'm gonna point out essential things that you have to consider in order to make yourself understand and start using Traits:
Flexibility: As shown in the USB example, traits allows you to write more generic functions that can handle different types of data. So you build the function 1 time, and it automatically works for any struct that you create in the future AS LONG AS it has the expected trait.
DRY (Don't repeat yourself): Traits allows you to write "default methods". So if 200 different structs share the same logic for a certain function, you can write the function's code inside the trait once. And all of the 200 different structs will INHERIT IT, saving it from copying and pasting code everytime you need an implementation of that function that is the same from the rest of the 200 structs.
Extensibility: You can also implement traits on structs that you didn't even write!!!. Let's make an example: If you import a library that contains
VideoPlayerstruct, you can write your own customTraitand attach it to theirVideoPlayer.Unlocking Built-in Rust Features: This one is really important since sometimes you want to use a function inside a crate and sometimes the compiler tells you that
method_xis not implemented or some shit. Well... Rust uses standard traits behind for almost everyting!!!. If you wanna print a struct into the screen, you have to implement theDisplayTrait. And if you wanna copy astruct, you implement theClonetrait. TRAITS are how you "talk" to the rust compiler. Remember this since it's very important...
Top comments (0)