Traits
A trait is a collection of methods defined for an unknown type: Self
.
// Define a trait named `Greet`
trait Greet {
// A method signature named `greet`
fn greet(&self);
}
// Implement the `Greet` trait for the `Person` struct
struct Person {
name: String,
}
impl Greet for Person {
// Implement the `greet` method for the `Person` struct
fn greet(&self) {
println!("Hello, my name is {}!", self.name);
}
}
fn main() {
// Create an instance of the `Person` struct
let person = Person {
name: String::from("Alice"),
};
// Call the `greet` method on the `person` instance
person.greet();
}
Derive
The compiler is capable of providing basic implementations for some traits via the #[derive]
attribute.
The following is a list of derivable traits:
- Comparison traits:
Eq
,PartialEq
,Ord
,PartialOrd
. Clone
, to createT
from&T
via a copy.Copy
, to give a type 'copy semantics' instead of 'move semantics'.Hash
, to compute a hash from&T
.Default
, to create an empty instance of a data type.Debug
, to format a value using the{:?}
formatter.
#[derive(PartialEq, PartialOrd)]
struct Duration(f64);
#[derive(Debug)]
struct S(i32);
Return Traits
The Rust compiler needs to know how much space every function's return type requires. This means all your functions have to return a concrete type.
Rust tries to be as explicit as possible whenever it allocates memory on the heap. So if your function returns a pointer that points to a trait on heap, you need to write the return type with the dyn
keyword, e.g. Box<dyn Drawable>
.
trait Drawable {
fn draw(&self);
}
struct Circle {
radius: f64,
}
impl Drawable for Circle {
fn draw(&self) {
println!("Drawing a circle with radius {}.", self.radius);
}
}
struct Square {
side_length: f64,
}
impl Drawable for Square {
fn draw(&self) {
println!("Drawing a square with side length {}.", self.side_length);
}
}
fn get_shape(is_circle: bool) -> Box<dyn Drawable> {
if is_circle {
Box::new(Circle { radius: 5.0 })
} else {
Box::new(Square { side_length: 8.0 })
}
}
fn main() {
let shape1 = get_shape(true);
let shape2 = get_shape(false);
shape1.draw();
shape2.draw();
}
Operator Overloading
In Rust, many of the operators can be overloaded via traits.
use std::ops::Add;
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
impl Add for Point {
type Output = Point;
fn add(self, other: Point) -> Point {
Point {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
fn main() {
let point1 = Point { x: 1, y: 2 };
let point2 = Point { x: 3, y: 4 };
let result = point1 + point2;
println!("Result: {:?}", result);
// Result: Point { x: 4, y: 6 }
}
Drop
The Drop
trait only has one method: drop
, which is called automatically when an object goes out of scope.
The main use of the Drop
trait is to free the resources that the implementor instance owns.
struct CustomResource {
data: String,
}
impl Drop for CustomResource {
fn drop(&mut self) {
println!("Dropping CustomResource with data: {}", self.data);
}
}
fn main() {
let resource = CustomResource {
data: String::from("learning on literank.com"),
};
println!("Using the CustomResource");
// At the end of this block, `resource` will go out of scope,
// and the `drop` method will be called automatically.
}
Iterators
The Iterator
trait is used to implement iterators over collections such as arrays.
struct Counter {
current: usize,
end: usize,
}
impl Iterator for Counter {
type Item = usize;
// Define the `next` method, which returns the next value in the sequence
fn next(&mut self) -> Option<Self::Item> {
if self.current < self.end {
let result = Some(self.current);
self.current += 1;
result
} else {
None
}
}
}
fn main() {
let counter = Counter { current: 0, end: 5 };
// Use the `for` loop with the iterator to iterate over the values
for num in counter {
println!("Count: {}", num);
}
// You can also use other methods provided by the `Iterator` trait
let squares: Vec<usize> = Counter { current: 0, end: 5 }
.map(|x| x * x)
.collect();
println!("Squares: {:?}", squares);
}
impl Trait
impl Trait
can be used in two situations:
- as an argument type
- as a return type
As Argument type
trait Printer {
fn print(&self);
}
fn print_shape(shape: impl Printer) {
shape.print();
}
As Return Type
trait Shape {
fn area(&self) -> f64;
}
struct Circle {
radius: f64,
}
impl Shape for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}
fn create_shape() -> impl Shape {
Circle { radius: 3.0 }
}
fn main() {
let shape = create_shape();
println!("Area: {}", shape.area());
}
Clone
When dealing with resources, the default behavior is to transfer them during assignments or function calls. However, sometimes we need to make a copy of the resource as well.
The Clone
trait helps us do exactly this.
#[derive(Debug, Clone)]
struct Person {
name: String,
age: u32,
}
fn main() {
let person1 = Person {
name: String::from("Alice"),
age: 20,
};
// Clone the `Person` instance to create a new one
let person2 = person1.clone();
// Print the original and cloned persons
println!("Original Person: {:?}", person1);
// Original Person: Person { name: "Alice", age: 20 }
println!("Cloned Person: {:?}", person2);
// Cloned Person: Person { name: "Alice", age: 20 }
}
Super Trait
Rust doesn't have "inheritance", but you can define a trait as being a "super trait" of another trait.
trait Printable {
fn print(&self);
}
// Define a sub trait named `DebugPrintable` that extends `Printable`
trait DebugPrintable: Printable {
fn debug_print(&self);
}
// Implement the `Printable` trait for the `Circle` struct
struct Circle {
radius: f64,
}
impl Printable for Circle {
fn print(&self) {
println!("Printing a circle with radius {}", self.radius);
}
}
// Implement the `DebugPrintable` trait for the `Circle` struct
impl DebugPrintable for Circle {
fn debug_print(&self) {
println!("Debug printing a circle with radius {}", self.radius);
}
}
fn main() {
// Create an instance of `Circle`
let circle = Circle { radius: 3.0 };
// Call methods from both traits
circle.print(); // Printing a circle with radius 3
circle.debug_print(); // Debug printing a circle with radius 3
}
Code Challenge
Try to modify the code provided in the editor to play with traits.