Notes on Traits and You: A Deep Dive Part 1
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
- 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: 101
On this post we will cover Traits 101, this will keep each post shorter and easier to digest.
In D&D there are many different races, but we'll start by using these 4 different races:
- Dwarves
- Elf
- HalfOrc
- Human
Creating our Structs
Now let's create these races as structs in Rust
struct Dwarf {
name:String
}
struct Elf {
name:String
}
struct HalfOrc {
name:String
}
struct Human {
name:String
}
As you can see this is pretty straight forward code where we define the struct name (Dwarf
, Elf
etc.) and then add the name:String
to allow our characters to be named.
Creating our Dwarf
First, let's create a Dwarf character.
let my_dwarf = Dwarf {
name: String::from("DanDwarf")
}
A variable my_dwarf
that stores our Dwarf
type and we name him DanDwarf
D&D Core Traits
In D&D each character has core traits:
- Strength
- Dexterity
- Constitution
- Intelligence
- Wisdom
- Charisma
If you are not familiar in D&D, character's core traits are defined in numbers, so an example would be that a character could have a Strength of: 8, a Dexterity of: 4, and a Constitution of 2, etc. as a base and depending on equipment or spells cast these traits could be boosted (or lowered).
Defining a Trait
Let's first define a Constitution
trait that we can use on our soon to be defined Characters
// Let's make a constitution trait
// where we define one function `constitution_bonus`
pub triat Constitution {
fn constitution_bonus(&self) -> u8;
}
Implementing Constitution
for Dwarf
So let's take one of these traits, in our case Constitution and define it in Rust for our Dwarf. We'll say that Dwarves have a Constitution bonus of 2.
//implemeting the trait
impl Constitution for Dwarf {
fn constitution_bonus(&self) -> u8 {
2 // this returns 2, anytime the instance of a dwarf is called.
}
}
Now that we've defined our trait we go back to our Dwarf, using our just defined constitution_bounus
that we can call on my_dwarf
as shown below.
// back to making the character
let my_dwarf = Dwarf {
name: String::from("DanDwarf")
};
my_dwarf.constitution_bonus(); // Returns 2
In this way no matter how many Dwarves we create, every single one of them will have the constitution_bonus
of 2
attached.
Creating a HalfOrc
Now, let's create a HalfOrc
and we'll start by implementing the constituion_bonus
, the Constitution bonus for a HalfOrc is 1
impl Constitution for HalfOrc {
fn constitution_bonus(&self) -> u8 {
1
}
}
This is the same as our Dwarf impl
but instead returning a 2, it returns 1
Now we create the HalfOrc with:
let my_half_orc = HalfOrc {
name: String::from("OscarOrc")
}
my_half_orc.constitution_bonus();// Returns 1
Pretty straightforward right? We bind my_half_orc
to our HalfOrc
and name him OscarOrc
and when we call the constitution_bonus
and we know that it will return 1
, as we have defined in the impl
block above.
Humans and Elves
Let's define our last 2 races of Humans and Elves, but let's say that both of these races have a constitution bonus of 0
.
Our first instinct might be to do the same as we did above and do this:
// we can implement like this but this is repetitive
impl Constitution for Elf {
fn constitution_bonus(&self) -> u8 {
0
}
}
impl Constitution for Human {
fn constitution_bonus(&self) -> u8 {
0
}
}
This is clearly repetitive and not ideal. Another thing to note is that most races actually have a constitution bonus of 0
So how do we deal with this?
Let's add a default value the Constitution
trait we first created.
// making `0` the default
pub triat Constitution {
fn constitution_bonus(&self) -> u8 {
0
}
}
In this way unless we have a struct that implements this trait and overrides this default value it will always return 0
We still have to implement the traits on the struct, but we don't have to define how the constitution bonus works since it's default value is 0
and Humans and Elves' constitution_bonus
matches the default value.
impl Constitution for Elf{
}
impl Constitution for Human{
}
Creating our Elf and Human
We know the drill here, we define both of these in the same way that we did for our my_dwarf
and my_half_orc
, the only difference is that since we have defined a default value on the Constitution
trait we have access to it automatically unless we further define the consitution_bonus
like we did previously.
let my_elf = Elf{
name:String::from("ElleElf")
};
my_elf.constituion_bonus(); // Retuns 0
let my_human = Human {
name:String::from("HarryHuman")
};
my_human.constitution_bonus(); // Returns 0
There it is, we have created 4 D&D characters and defined their constitution_bonus
in a couple of different ways using Triats.
Summary
Let's summarize what we did here, in a quick step-by-step so we can see how easy it actually is to use traits in this way.
- Create a
struct
for each of the races. - Create a character with a variable
my_<race>
and defined it's type and name. - Create a
trait
, in our caseconstitution_bonus
- Implement the
triat
on theDwarf
struct- define constitution bonus as
2
- calling
constitution_bonus()
on our character it returns2
- if we create more
Dwarf
characters they'd all have a constitution bonus of2
- define constitution bonus as
- Implement the
trait
on theHalfOrc
struct- define constitution bonus as
1
- calling
constitution_bonus()
on our character returns1
- if we create more
HalfOrc
characters they'd all have a constitution bonus of1
- define constitution bonus as
- Add a default value to our
Constitution
trait - Implement the
trait
onElves
andHumans
(with no additional definition) - Created our
Elves
andHumans
- calling
constitution_bonus()
on our elf and human characters returns0
- creating other races with no function definition in their implementation will always return
0
- calling
Alright, as you can see there's not a lot of tricky parts here, it's all pretty straight forward, especially when laid out in this way where we can see how easy it is, in a few easy steps start using Traits in Rust.
Next we'll dive into Traits 201, it will build on what we've learned here with Trait bounds. See you next time.
Update 2023-03-14:If you're ready for Part 2, it's ready for you.