Rust Trait by Example

Xiaodong Yan
3 min readDec 28, 2023

I’m excited to share a couple of topics on effectively utilizing traits in Rust.

Supertrait

In Rust, a supertrait is a trait that imposes additional requirements beyond those specified by another trait. A supertrait extends the functionality of a trait by adding more methods or functionality that the implementing types must fulfill. It establishes a relationship where any type implementing the subtrait must also implement its supertrait.

For example:

trait Area {
fn calculate_area(&self) -> f64;
}

// Area is a supertrait of Shape, Shape is a subtrait of Area
trait Shape: Area {
fn describe(&self);
}

struct Rectangle {
length: f64,
width: f64,
}

impl Area for Rectangle {
fn calculate_area(&self) -> f64 {
self.length * self.width
}
}
// To impl Shape we must impl its supertrait Area like above
impl Shape for Rectangle {
fn describe(&self) {
println!("I am a rectangle with length {} and width {}", self.length, self.width);
// We can access supertrait's method using self
println!("Area: {}", self.calculate_area());
}
}

fn print_shape_info<T: Shape>(shape: &T) {
shape.describe();
}

fn main() {
let rectangle = Rectangle { length: 4.0, width: 3.0 };

print_shape_info(&rectangle);
// Output:
// I am a rectangle with length 4 and width 3
// Area: 12
}

Trait Extension

Trait extension in Rust allows you to add new methods or behaviour to existing types by implementing traits for those types. It’s a powerful concept that enables you to extend the functionality of types without modifying their original definitions.

Here’s an example to illustrate trait extension:

// Define a trait with a method
trait Hello {
fn say_hello(&self);
}

// Implement the trait for the type i32
impl Hello for i32 {
fn say_hello(&self) {
println!("Hello, I'm {}!", self);
}
}

fn main() {
let number = 42;
number.say_hello(); // Calling the method from the trait extension
}

Blanket Implementation

Blanket Implementation is a generic extension that allows you to conditionally implement a trait for any type that implements another trait:

use std::fmt::Debug;

trait Displayable {
fn display(&self);
}

impl<T: Debug> Displayable for T {
fn display(&self) {
println!("Hello {:?}", self);
}
}

#[derive(Debug)]
struct MyStruct {
value: i32,
}

fn main() {
let instance = MyStruct { value: 42 };

instance.display(); // This will print: Hello MyStruct { value: 42 }
}

Method Disambiguation

Rust compiler prevents ambiguity in method resolution when a type implements multiple traits with conflicting methods.

Here is an example that introduces this scenario and corresponding solutions:

  1. Explicit casting to access a specific trait’s methods
  2. Using a trait object
trait A {
fn greet(&self) -> &'static str {
"Hello from A"
}
}

trait B {
fn greet(&self) -> &'static str {
"Hello from B"
}
}

struct C;

impl A for C {}
impl B for C {}

fn main() {
let c = C;
// println!("{}", c.greet()); // Compiling error: multiple `greet` found

// Option 1: Explicit casting to access a specific trait's methods
println!("{}", A::greet(&c)); // Output: "Hello from A"
println!("{}", B::greet(&c)); // Output: "Hello from B"

// Option 2: Convert c to a trait object of type A
let a: &dyn A = &c;
println!("{}", a.greet()); // Output: "Hello from A"
}

Reference

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Xiaodong Yan
Xiaodong Yan

Written by Xiaodong Yan

Architect working in media industry @Toronto

Responses (1)

Write a response

Nice article!