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.
When implementing traits in Rust, the where
clause comes in handy to impose restrictions on the generic types utilized in the trait. Rust is a language that promotes intentional programming, and it offers tools and features to support this paradigm. The where
clause is an example of such a feature that allows developers to apply extra constraints to enhance the predictability of software development in Rust.
By utilizing the where
clause, developers can specify precise requirements on the types used in their code, resulting in code that is more reliable, easier to maintain, and less prone to errors.
In this article, you’ll learn why you need the where clause and how to use it.
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, 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

Subscribe to LeanDev now to get weekly curated content from engineering leaders in your inbox to help you become a better developer. Subscribe Now