- Lab
- Core Tech

Guided: Intro to Traits in Rust
The purpose of this lab to introduce the fundamental concept and usages of traits in Rust. Traits promote reusability and polymorphism in Rust and will be used in this lab to develop an expense tracker application. By the end of this lab, you should have a solid understanding of how traits can typically be used in Rust and how they can add further depth and utility to your applications.

Path Info
Table of Contents
-
Challenge
Introduction
The goal of this lab is to provide an introduction to the concept of traits in Rust. Traits are an incredibly powerful and flexible feature in Rust, with uses that include abstraction and code reusability, among other things.
Throughout the course of this lab, you will be introduced to the definition and implementation of traits, along with some useful ways to utilize them, in the form of trait objects and bounds.
At the end of the lab, you will incorporate your usage of traits into the main application logic of an expense tracker app to make it fully functional. You can then run the application by clicking the Run button or by entering
cargo run
into the Terminal.Feel free to also check out the solution directory if you get stuck
info> Note: There are two files labeled
mod.rs
open in the editor. Hovering over each file tab will display the entire file path, helping you determine which file you need to work within. -
Challenge
Step 1: Defining and Implementing Traits
A trait in Rust is a definition for certain behavior that can be shared among multiple types. For example:
trait MyTrait { fn my_method(); fn method_two(param: i32) -> i32; }
The above code block is a trait definition that specifies a trait called
MyTrait
. It defines two methods, so any type that implements this trait must provide an implementation for those two methods. In other words, any custom type or struct can implementMyTrait
as long as they provide some implementation formy_method
andmethod_two(param:i32) -> i32)
. You may recognize this behavior as something similar to interfaces from other languages, which is correct. In a simple way, traits are just Rust's version of interfaces.However, traits are special in Rust in that they are more powerful and flexible than interfaces typically are in other languages, which you'll get to later. Traits can also provide default implementations for their methods. When a default implementation is provided, any type that implements the trait does not have to provide an implementation for that method. If the method is called, the type will simply use the default implementation.
However, you can still overwrite any default implementation with your own implementation for the type to use.
-
Challenge
Step 2: Implementing Traits
Implementing traits for a type is quite simple. Syntactically it should look like the following:
impl MyTrait for MyStruct { Provide method implementations here }
Within the
impl
block you must provide method implementations for the trait. However, you are not required to provide an implementation for methods with default implementations unless you wish to overwrite it with your own. Now let's talk about thederive()
macro. Rust essentially has a few "built-in" traits that you can utilize, one of which isDebug
. By using thederive()
macro to implementDebug
on a custom type or struct, you can now print it usingprintln()
with{:?}
debug formatting. This is a contrast to the typical{}
display format, which requires manual implementation of theDisplay
trait for custom types. WhileDebug
won't be as pretty asDisplay
, it provides a quick and simple solution for development when you just want to see the contents of a type.The macro is usually called as follows:
#[derive(Debug)] struct MyStruct
You can also call multiple of Rust's "built-in" traits besides
Debug
as parameters forderive()
.The
derive()
macro will be useful here because you will need to implement theDebug
trait in order to implement theprint_debug()
method forExpense
. -
Challenge
Step 3: Trait Objects
A key theme with usage of traits is reusability and shared behavior. There may be times where you want to work with the functionality of a trait, regardless of the types that implement the trait. This is where trait objects come into play.
A trait object is a way to work with values of different concrete types that implement a common trait. To handle allocation and specific implementations, Rust employs a mechanism known as dynamic dispatch to determine the actual type at runtime in conjunction with a smart pointer known as
Box<>
to point to the type on the heap. Here is an example:trait MyTrait { code here } struct FirstStruct { code here } struct SecondStruct { code here } impl MyTrait for FirstStruct { code here } impl MyTrait for SecondStruct { code here } struct ThirdStruct { my_field: Box<dyn MyTrait> }
In this example, you have a trait defined as
MyTrait
. BothFirstStruct
andSecondStruct
implementMyTrait
, even if they may have different implementations for the trait. Any instance of bothFirstStruct
orSecondStruct
would be a valid value formy_field
inThirdStruct
, since both of them implementMyTrait
. Any type that doesn't implementMyTrait
however, could not be set as a value formy_field
. -
Challenge
Step 4: Trait Bounds
Traits can also be used with generics in the form of trait bounds. Take a look at the following examples:
fn func_one<T: MyTrait>(P: &T) { method body here } fn func_one<T: MyTrait>() -> T { method body here }
Both of these generic methods utilize trait bounds, which specify that
T
must be a type that implementsMyTrait
.This is useful in your application if you look in
main.rs
. The functionget_input()
currently only returnsi32
, but you would have to make multiple versions ofget_input()
if you also want it to retrieve strings or floats. To remedy this, you can makeget_input()
a generic method so it can return any of those types using theparse()
method. You can specify astd::str::FromStr
trait bound for these types to ensure that the typesget_input
returns are parsable from strings. After completing these tasks, the expense tracker app is now complete. Running the application will now give you a menu that lets you add new expenses to your expense tracker or list all the expenses in the tracker. Listing all expenses will also display a total expense value that is a sum of all the current expenses.Completing this lab should have given or reinforced a fundamental understanding of traits in Rust as well as given you an idea on some of the ways they can be utilized to supplement your code.
What's a lab?
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Provided environment for hands-on practice
We will provide the credentials and environment necessary for you to practice right within your browser.
Guided walkthrough
Follow along with the author’s guided walkthrough and build something new in your provided environment!
Did you know?
On average, you retain 75% more of your learning if you get time for practice.