Hello! Welcome to the second lesson in our course on Rust traits.
In the last session, we learned how to define a trait, which acts like a contract or a set of rules for behavior. You created a Drawable trait with a draw method signature, establishing the what but not the how.
Today, we'll focus on that "how." This lesson is all about fulfilling the contract. You will learn how to implement a custom trait for a struct type. This is where you provide the concrete code that brings your abstract trait definitions to life. By the end of this session, you'll be able to connect a trait to a struct and define its specific behavior.
The impl Keyword: Fulfilling the Contract
To make a struct adopt the behavior of a trait, you use a special block of code that starts with the impl keyword. The syntax is a straightforward way of telling Rust, "I am now providing the implementation of this trait for this struct."
The general structure looks like this:
impl TraitName for StructName {
// a block of code with the method implementations
}
Inside this block, you write the full function bodies for the method signatures you defined in the trait. Remember how the trait method signature ended with a semicolon? Here, you replace that semicolon with a pair of curly braces {} containing the actual code.
For a clear explanation of this syntax, please read the following section from the article "Understanding Rust Traits: For a beginner".
Understanding Rust Traits: For a beginner | by Mir luqman
This section explains the core syntax for connecting a trait to a struct using impl...for....
Read the sections titled 'Making a Struct Follow the Rules' and the explanation that follows the code block. Focus on understanding how impl Summary for NewsArticle connects the trait to the struct.
Implementing a Trait: A Practical Example
Now let's see this in action. The best way to understand implementation is to see how different structs can implement the same trait in their own unique ways.
The following video demonstrates how to implement the Summary trait (which we saw being defined in the last lesson) for two different structs: NewsArticle and Tweet.
This clip from the 'Traits in Rust' video by Let's Get Rusty shows the implementation of a Summary trait for two distinct structs.
Watch from 01:52 to 02:43. Pay attention to the two separate impl blocks. Notice how the summarize method is implemented differently for NewsArticle than it is for Tweet.
As you saw, even though both NewsArticle and Tweet implement the Summary trait, the output of their summarize methods is tailored to their specific data:
- For
NewsArticle, it combines theheadlineandauthor. - For
Tweet, it combines theusernameandcontent.
This is the core power of traits: defining a common ability (summarize) while allowing for different, type-specific implementations.
Your Turn: Practice Implementing a Trait
Now it's time to apply what you've learned. Your goal is to fill in the missing impl blocks to make the program work as intended. The assert_eq! macros in the main function will tell you exactly what the output of each method should be.
This exercise is a direct application of the impl Trait for Type pattern.
The following resource from Rust By Practice provides an excellent hands-on exercise for implementing a trait.
Find the section titled 'Exercises'. Copy the code block into your own Rust environment (like the Rust Playground at https://play.rust-lang.org/) and fill in the two empty impl blocks to make the code compile and run successfully.
Test your understanding!
After completing the exercise, think about this: Why did you need to implement say_hi for the Teacher struct but not for the Student struct?
Show answer
The Hello trait provides a default implementation for the say_hi method: fn say_hi(&self) -> String { String::from("hi") }.
- The
Studentstruct can use this default implementation directly, so you don't need to provide a new one. - The
Teacherstruct required a different message ("Hi, I'm your new teacher"), so you had to override the default implementation with a custom one inside itsimplblock.
We haven't explicitly covered default implementations yet, but this exercise is a great way to discover them. They are a powerful feature for reducing code duplication.
Conclusion
Great work! You've now mastered the second fundamental piece of using Rust traits. You took an abstract contract (a trait) and provided the concrete code to fulfill it for a struct.
Here are the key takeaways from this lesson:
- Use
impl Trait for Type: This is the syntax for implementing a trait for a specific type. - Provide Method Bodies: Inside the
implblock, you write the full implementation for each method signature required by the trait. - Custom Behavior: Different types can implement the same trait, but each can provide its own unique logic for the trait's methods.
In our final lesson, we will complete the puzzle. Now that you can define a trait and implement it, we will learn how to instantiate a struct and call a method from its implemented trait, finally putting our shared behavior to use.