Skip to main content
Rust Programming
CHAPTER 17 Beginner

Traits and Generics

Updated: May 18, 2026
5 min read

# CHAPTER 17

Traits and Generics

1. Chapter Introduction

If you are building an application with User 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

rust
12345
// Define a Trait named Summary
pub trait Summary {
    // Method signature only! No body.
    fn summarize(&self) -> String;
}

2. Implement the Trait on Structs

rust
1234567891011121314151617181920212223
struct Article {
    headline: String,
    author: String,
}

struct Tweet {
    username: String,
    content: String,
}

// Implement the trait for Article
impl Summary for Article {
    fn summarize(&self) -> String {
        format!("{}, by {}", self.headline, self.author)
    }
}

// Implement the trait for Tweet
impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("@{} tweeted: {}", self.username, self.content)
    }
}

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.

rust
1234
// <T> means this function takes some abstract type T
fn display_anything<T>(item: T) {
    // (We will see why this doesn't compile perfectly in a second!)
}

5. Trait Bounds (Fixing Generics)

If we write fn 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*."

rust
1234567891011121314
// <T: Summary> is the Trait Bound!
fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

fn main() {
    let tweet = Tweet {
        username: String::from("alice"),
        content: String::from("Rust is awesome!"),
    };

    // We can pass our tweet to the generic function!
    notify(&tweet);
}

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.
rust
123456789
pub trait Summary {
    // Default implementation
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

// Empty implementation block uses the default!
impl Summary for Article {}

7. Generic Structs

You can also use Generics in Structs. The Option<T> enum and Vec<T> collection use this extensively.
rust
12345678910
// A Point that can hold either integers or floats
struct Point<T> {
    x: T,
    y: T,
}

fn main() {
    let integer_point = Point { x: 5, y: 10 };
    let float_point = Point { x: 1.0, y: 4.0 };
}

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 User struct from another file but forget to import its Summary trait, 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 if T is 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 {:?}) and Display (allows printing with {}).

10. Exercises

  1. 1. Create a trait called Vehicle with a method honk() -> String.
  1. 2. Create a Car struct and a Truck struct. Implement Vehicle for both.
  1. 3. Write a generic function make_sound<T: Vehicle>(v: &T) that prints the honk.

11. MCQs with Answers

Question 1

What is the Rust equivalent of an Interface in Java/C#?

Question 2

What does a Trait primarily contain?

Question 3

What keyword is used to attach a Trait to a specific Struct?

Question 4

What is a Generic?

Question 5

Why does fn print<T>(item: T) fail if you try to call item.summarize()?

Question 6

How do you fix the above issue?

Q7. Can a Trait provide a default implementation for a method? a) Yes b) No, they can only contain empty signatures Answer: a) Yes.

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.

Question 10

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?

13. Summary

Traits and Generics allow you to write incredibly flexible, DRY (Don't Repeat Yourself) code. By defining abstract contracts via Traits and enforcing them via Trait Bounds on Generics, Rust achieves robust Polymorphism without the deep, confusing inheritance hierarchies found in older OOP languages.

14. Next Chapter Recommendation

You have mastered the basics of the Borrow Checker. But what happens when you try to store a borrowed reference *inside* a Struct? The compiler will demand you explain how long that reference will live. In Chapter 18: Lifetimes in Rust, we will conquer the final boss of Rust's memory system.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·