Trait Bounds and Generics
Rust supports generic types, but due to the its strict type checker we need to put some restrictions on the generic type. In Python, we can use unions to signify that a variable or argument can be of different types.
def display(s: str | int | list[str]):
print(s)
print('my_string')
print(1)
print(['my', 'string'])
# This will actually run but the linter will complain.
print({'my', 'string'})
In Rust, this works a bit differently. To print something, the variable needs to implement either the Display
trait ("normal" print) or the Debug
trait (debug print). By using a trait bound
, we can tell the compiler that only generic types which implement the Display
or Debug
trait are allowed as argument(s) to the function.
In the example below, we implement a function that accepts an argument of a generic type T
that implements the Debug
trait. Luckily, all Rust types in the std
library automatically implement Debug
.
use std::fmt::Debug; fn display<T: Debug>(arg: T) { println!("{:?}", arg); } fn main() { display(1); display("my_string"); display("my_string".to_string()); display(vec!["my", "string"]); }
Deriving traits
What if we have a type that does not implement Debug
by default? We can derive it using the derive
macro.
In the following example, we'll create a Struct
that by default does not implement Debug
. By using the derive
macro, we can subsequently call our display
function.
use std::fmt::Debug; #[derive(Debug)] // Try commenting this out! struct MyStruct<'a> { field_1: usize, field_2: &'a str, field_3: String, field_4: Vec<&'a str>, } fn display<T: Debug>(arg: T) { println!("{:?}", arg); } fn main() { let my_struct = MyStruct { field_1: 1, field_2: "my_string", field_3: "my_string".to_string(), field_4: vec!["my", "string"], }; display(my_struct); }
Other common Rust traits that can be derived or implemented manually are: