Notes on Traits and You: A Deep Dive Part 2
Original content by Nell Shamrell-Harrington
When looking for resources to help me better understand Rust Traits, I found this videofrom Nell Shamrell-Harrington. It's a great intro and overview into using Traits, which can be a little confusing when you first start learning Rust.
So these are my notes on this talk, if you haven't watched it and are struggling with learning Traits, I strongly suggest that you watch the video above before you do anything else. But if you're short on time and just want to reference my notes -- here they are. Disclaimer: these notes are my own interpretation of the talk and may not necessarily reflect the views of the original speaker. These note are primarily for me to learn these concepts, and I publish them for others to see and maybe benefit from. Feel free to let me know if I have errors or have misrepresentations.
Introduction
The talk covers 3 different ways that we can use Traits, I have already written my first part of these notes, you can see those linked below.
- Traits 101 Introduction
- Traits 201 Trait Bounds
- Traits 301 Trait Objects
Nell brilliantly uses D&D as a metaphor to help explain Traits. It should be clear that the use of D&D is just to help grasp some concepts, and the D&D rules do not strictly apply in these concepts, so now that we have that out of the way let's begin.
Traits 201: Trait Bounds
Today we cover Traits Bounds. In my previous notes we used 4 different races to explain how traits can be used with those races. Now, we're going to add a 5th race: the Half-Elf.
Here's our full list:
- Dwarves
- Elf
- HalfOrc
- Human
- Half-Elf
Traits 101
In our previous note we went through the process of how to define a Trait, we used Constitution
as the trait we wanted to apply to the 4 different races in a couple of different ways. If you haven't read that note, it might help to do so first to help better understand the basics of creating our characters, creating a Trait, defining race specific values as well as default values.
Trait Bounds
To help us understand Trait bounds, let's look at how these races communicate. Each race has core languages that they speak, for example.
- Dwarves: Common, Dwarvish
- Elf: Common, Elvish
- Half-Elf: Common, Elvish
Defining the Elvish
language Trait
Let's focus on the Elvish language first and define a trait for the language and define a impl
for the Elf
and HalfElf
races.
// Define the Elvish trait
pub trait Elvish {
}
// Implement the Elvish trait for the Elf struct
impl Elvish for Elf {
}
// Implement the Elvish trait for the HalfElf struct
impl Elvish for HalfElf {
This should be pretty straight-forward so far as it's just like we started Traits 101.
Now let's make a function to allow our characters to actually speak Elvish
pub fn speak_elvish(character: T) -> String{
String::from("yes")
}
Right now this function can be called on anything with no arguments, so we add an argument called character, but this character doesn't need to be a specific type it can be any of the structs we've already created.
But if we add the <T: Elvish>
this means we will only accept the Elvish
trait.
pub fn speak_elvish<T: Elvish>(character: T) -> String {
String::from("yes")
}
let my_elf = Elf { name: String::from("ElleElf")};
// we use our previously defined variable with the `speak_elvish` function
speak_elvish(my_elf)
So now if we call our Elf
(my_elf
) with speak_elvish(my_elf)
it would return a yes
because we have implemented Elvish
for Elf
.
Let's look at our HalfElf
// This is the same function that we defined before
pub fn speak_elvish<T: Elvish>(character: T) -> String {
String::from("yes")
}
// here we bind our HalfElf to `my_half_elf`
let my_half_elf = HalfElf { name: String::from("HarryHalfElf")};
speak_elvish(my_half_elf) // again this returns 'yes' since we've implemented `Elvish` for `HalfElf`
So far so good right, I hope it's pretty easy to see why both the my_elf
and my_half_elf
both return yes
when we call them using the speak_elvish
function.
HalfOrcs Incoming
We've successfully implemented our speak_elvish
trait on both our Elf
type races, but let's see what happens when we try to call our HalfOrc.
pub fn speak_elvish<T: Elvish>(character: T) -> String {
String::from("yes")
}
let my_half_orc = HalfOrc { name: String::from("OscarOrc")};
speak_elvish(my_half_orc)
So what happens if wet try to call my_half_orc
with speaks_elvish
? Well in this case our code would not compile because we have not implemented Evlish
for HalfOrc
. Of course, Orcs tend to break everything 😉.
Recap
Trait bounds allow a function to only accept types that implement a certain trait or combination of traits. This provides a powerful way to enforce specific behaviors and characteristics for the types used in your functions. Furthermore, you can combine multiple traits as criteria for a function, allowing for even greater flexibility and control.
Let's walk through each step like we did in Traits 101 on how we define Trait bounds:
- Define our Trait in our case it was the language trait
Elvish
- Implement
Elvish
for the races that speak it, in our caseElf
andHelfElf
. - Define a function
speak_elvish
that only accepts the race we define within the function. In our case we did it for bothElf
andHalfElf
. This function returns ayes
if we call a race that hasElvish
implemented, for example:speak_elvish(my_half_elf)
- If we try to do the same with our
HalfOrc
:speak_elvish(my_half_orc)
, our code won't compile.
This was a short and straightforward introduction to Trait Bounds, and we'll dive deeper into Rust Traits in the upcoming post on Traits 301: Trait Objects, where the real magic happens