Traits and Generics
# CHAPTER 17
Traits and Generics
1. Chapter Introduction
If you are building an application withUser structs, Product structs, and Invoice structs, you might want all of them to have a summarize() function. Instead of writing duplicate code, we can define a shared behavior contract. In Java or C#, this is an Interface. In Rust, it is a Trait. Furthermore, if we want a function to accept *any* struct that has this trait, we use Generics.
2. Learning Objectives
By the end of this chapter, you will be able to:- Understand and define Traits.
- Implement a Trait on custom Structs.
-
Write functions that use Generics (
<T>).
- Constrain Generics using Trait Bounds.
3. Defining and Implementing a Trait
A Trait defines a set of method signatures. It acts as a contract.1. Define the Trait
2. Implement the Trait on Structs
Now, both Article and Tweet possess the .summarize() method, even though they hold completely different data!
4. Generics (<T>)
Generics allow you to write functions or structs that can handle multiple data types without duplicating code.
Imagine you want a function that returns the largest of two values. Instead of writing largesti32(a: i32, b: i32) and largestf64(a: f64, b: f64), you use a Generic Type Parameter, conventionally named T.
5. Trait Bounds (Fixing Generics)
If we writefn print_summary<T>(item: T), the compiler will panic. Why? Because the compiler doesn't know if the abstract type T actually possesses a summarize() method!
We must constrain the Generic using a Trait Bound. We tell the compiler: "This function accepts any type T, *as long as T implements the Summary trait*."
6. Default Trait Implementations
You can actually provide a default body for a trait method. If a struct implements the trait but doesn't provide its own version, it falls back to the default.7. Generic Structs
You can also use Generics in Structs. TheOption<T> enum and Vec<T> collection use this extensively.
8. Common Mistakes
-
Missing Trait Imports: If you want to use a Trait's methods on a struct, the Trait *must* be in scope. If you import a
Userstruct from another file but forget to import itsSummarytrait, the compiler will say.summarize() not found.
-
Forgetting Trait Bounds: Writing
<T>and trying to do math on it (T + T). The compiler will stop you because it doesn't know ifTis a number or a String! You must bound it (e.g.,<T: std::ops::Add>).
9. Best Practices
-
Implement Standard Traits: Rust has many standard traits you should implement for your structs, such as
Debug(allows printing with{:?}) andDisplay(allows printing with{}).
10. Exercises
-
1.
Create a trait called
Vehiclewith a methodhonk() -> String.
-
2.
Create a
Carstruct and aTruckstruct. ImplementVehiclefor both.
-
3.
Write a generic function
make_sound<T: Vehicle>(v: &T)that prints the honk.
11. MCQs with Answers
What is the Rust equivalent of an Interface in Java/C#?
What does a Trait primarily contain?
What keyword is used to attach a Trait to a specific Struct?
What is a Generic?
Why does fn print<T>(item: T) fail if you try to call item.summarize()?
How do you fix the above issue?
Q8. Can a Struct have a Generic type, like struct Point<T>?
a) Yes b) No
Answer: a) Yes.
Q9. If a Struct implements a Trait that has a default method, do you have to write custom logic for that method?
a) Yes b) No, you can provide an empty impl block to use the default
Answer: b) No, you can use the default.
What is the benefit of Generics?
12. Interview Questions
- Q: Explain Polymorphism in Rust. How is it achieved without Class Inheritance? (Answer: Through Traits and Generics).
- Q: What is a Trait Bound, and why is it necessary for compilation safety?