Rust trait implementation with a “where” clause

Rust trait implementation with a “where” clause
Spread the love

In today’s Rust Journey piece, we’ll discuss one concept that might confuse new Rust Developers — the where clause in the Rust trait implementation workflow. The where clause allows developers to enforce strict constraints on generic types which will result to code that is reliable, easier to maintain, and less prone to errors.

While you can use the where clause in different context, our examples will be focused on how to use it in a trait implementation. You can use similar idea to implement it for other types.

If you need a refresher on Rust traits, by all means, take a look at the MIT Rust Book, it’s detailed and practical.

Let’s start with a high-level overview and drive the narrative down to the implementation details:

The problem: build a system that allows a user to enter their last name, first name, and age as input and return their full name and age as the result of the operation. It should be possible to compare the ages of different users.

First, we’ll define a struct that will accept the necessary parameters, but to demonstrate the where clause, we’ll make the type of age generic.

struct User<T> {
    first_name: String,
    last_name: String,
    age: T
}

Afterward, we’ll define a trait with a method name that will concatenate the first and last name and return the user’s full name.


trait FullName {
    fn name(&self) -> String;
}

Next, we’ll implement the trait for the User struct since the trait method above will do nothing if we don’t implement it.


impl <T> FullName for User<T> where T: std::cmp::PartialOrd {
    fn name(&self) -> String {
        format!("{} {}", self.first_name, self.last_name)
    }
}

Yea, we finally used the where clause. But why are we using it? Simple. We want to make sure that whatever type the age is, it must be a type that implements PartialOrd

What is PartialOrds anyways?

PartialOrd is a Rust trait that defines a partial ordering between values, allowing for some values to be incomparable.

Examples of types that implement PartialOrd are all primitive integer types u8, u16, u32, u64, etc, and floating point number types like f32 and f64.

We want to make sure that our generic type T implements PartialOrd which will allow us to compare the age of two or more users. PartialOrd here is just an example, you can define any trait you want.

Finally, let’s pass the user data and call the name method to give us the user’s full name in the main function as shown below:


fn main() {
    let user_1 = User::<u32> {
        first_name: "Okon".to_string(),
        last_name: "Bassey".to_string(),
        age: 20
    };
        let user_2 = User::<u32> {
        first_name: "Darlinton".to_string(),
        last_name: "Tunde".to_string(),
        age: 30
    };

    println!("{}, and {} are {} and {} old respectively", user_1.name, user_2.name, user_1.age, user_2.age);

    match user_1.age > user_2.age{
        true => println!("{} is older than {}", user_1.name(), user_2.name() ),
        false => println!("{} is older than {}", user_2.name(), user_1.name() ),
    }
}

If one tries to use the User struct with a type other than one that implements a PartialOrd it will throw an error. That’s about it, here is the complete code:


struct User<T> {
    first_name: String,
    last_name: String,
    age: T
}

trait FullName {
    fn name(&self) -> String;
}

impl <T> FullName for User<T> where T: std::cmp::PartialOrd {
    fn name(&self) -> String {
        format!("{} {}", self.first_name, self.last_name)
    }
}

fn main() {
    let user_1 = User::<u32> {
        first_name: "Okon".to_string(),
        last_name: "Bassey".to_string(),
        age: 20
    };
    let user_2 = User::<u32> {
        first_name: "Darlinton".to_string(),
        last_name: "Tunde".to_string(),
        age: 30
    };

    println!("{}, and {} are {} and {} old respectively", user_1.name(), user_2.name(), user_1.age, user_2.age);

    match user_1.age > user_2.age{
        true => println!("{} is older than {}", user_1.name(), user_2.name() ),
        false => println!("{} is older than {}", user_2.name(), user_1.name() ),
    }
}

There are very good reasons you have chosen to use Rust for development and type safety is most likely one of them. Using the where clause appropriately can help ensure type safety. You can learn more from The Rust Book

Buy Me A Coffee

Published by Eze Sunday Eze

Hi, welcome to my blog. I am Software Engineer and Technical Writer. And in this blog, I focus on sharing my views on the tech and tools I use. If you love my content and wish to stay in the loop, then by all means share this page and bookmark this website.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.