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

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:

  1. Dwarves
  2. Elf
  3. HalfOrc
  4. 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.

  1. Create a struct for each of the races.
  2. Create a character with a variable my_<race> and defined it's type and name.
  3. Create a trait, in our case constitution_bonus
  4. Implement the triat on the Dwarf struct
    • define constitution bonus as 2
    • calling constitution_bonus() on our character it returns 2
    • if we create more Dwarf characters they'd all have a constitution bonus of 2
  5. Implement the trait on the HalfOrc struct
    • define constitution bonus as 1
    • calling constitution_bonus() on our character returns 1
    • if we create more HalfOrc characters they'd all have a constitution bonus of 1
  6. Add a default value to our Constitution trait
  7. Implement the trait on Elves and Humans(with no additional definition)
  8. Created our Elves and Humans
    • calling constitution_bonus() on our elf and human characters returns 0
    • creating other races with no function definition in their implementation will always return 0

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.