Traits in Rust

December 22, 2025

In this post, we'll explore traits in Rust - a powerful way to define shared behavior across different types, enabling polymorphism and code reuse.

Before diving into traits, make sure you understand:

Once you've mastered traits, continue your learning journey with:

Before We Start: What Problem Are We Solving?

Remember at the end of Generics, we had a problem?

fn largest<T>(list: &[T]) -> &T {
    let mut largest = &list[0];
    for item in list {
        if item > largest {  // ERROR!
            largest = item;
        }
    }
    largest
}

Rust refuses to compile this. It says:

error: binary operation `>` cannot be applied to type `T`

Why?

Because T can be ANY type. And not every type can be compared with >.

Think about it:

Generics are TOO flexible here. We need to say:

"I want any type... BUT it must be a type that can be compared."

This is exactly what traits do.


Step 1: What Is a Trait?

A trait defines a capability: something a type can DO.

Real-World Analogy

Think about job requirements.

If you're hiring, you don't just say "I need a person." You say:

These are capabilities. Different people have different capabilities. A software engineer can write code. A translator can speak Spanish. A trucker can drive a truck.

Traits work the same way:

These are capabilities that types can have.

Another Analogy

Think about electrical plugs.

Different devices have different plugs:

But they all share one capability: they can connect to a power outlet and receive electricity.

If you're building a power strip, you don't care WHAT device gets plugged in. You just care that it HAS A PLUG.

A trait is like saying "has a plug", it's a capability that different types can share.


Step 2: Defining a Simple Trait

Let's create our first trait. We'll start with something simple.

The Scenario

Imagine we're building a program that displays information about different things:

They're all different types, but they share one capability: they can make a sound.

Defining the Trait

trait MakesSound {
    fn sound(&self) -> String;
}

That's it! We've defined a trait.

Breaking It Down

trait MakesSound {
//^^^
//Keyword to define a trait
trait MakesSound {
//    ^^^^^^^^^^
//    The name of our trait
trait MakesSound {
    fn sound(&self) -> String;
//  ^^
//  This trait has a method
}
trait MakesSound {
    fn sound(&self) -> String;
//     ^^^^^
//     Method name
}
trait MakesSound {
    fn sound(&self) -> String;
//           ^^^^^
//           Takes a reference to self
}
trait MakesSound {
    fn sound(&self) -> String;
//                     ^^^^^^
//                     Returns a String
}
trait MakesSound {
    fn sound(&self) -> String;
//                           ^
//                           Semicolon! No body!
//                           We only define the signature, not the implementation
}

What This Means

The trait says:

"Any type that implements MakesSound MUST have a method called sound that takes &self and returns a String."

It's a contract. A promise. If a type says "I implement MakesSound," it MUST provide this method.

Why No Method Body?

The trait only defines WHAT the method looks like (its signature). It doesn't say HOW the method works.

Why? Because different types will implement it differently:

The trait just says "you must have this method." Each type decides what the method actually does.


Step 3: Implementing a Trait on a Type

Now let's make some types that implement our MakesSound trait.

Creating a Dog That Makes Sound

First, we define a struct:

struct Dog {
    name: String,
}

Now we implement the trait for this struct:

impl MakesSound for Dog {
    fn sound(&self) -> String {
        String::from("Woof!")
    }
}

Breaking Down the impl Block

impl MakesSound for Dog {
//^^
//Keyword for implementing
impl MakesSound for Dog {
//   ^^^^^^^^^^
//   The trait we're implementing
impl MakesSound for Dog {
//              ^^^
//              Keyword connecting trait to type
impl MakesSound for Dog {
//                  ^^^
//                  The type that gets the trait
impl MakesSound for Dog {
    fn sound(&self) -> String {
//  ^^^^^^^^^^^^^^^^^^^^^^^^^
//  We MUST provide this method (the trait requires it)
        String::from("Woof!")
//      ^^^^^^^^^^^^^^^^^^^^^
//      The actual implementation, what this method DOES for Dog
    }
}

The Full Example

trait MakesSound {
    fn sound(&self) -> String;
}

struct Dog {
    name: String,
}

impl MakesSound for Dog {
    fn sound(&self) -> String {
        String::from("Woof!")
    }
}

fn main() {
    let my_dog = Dog { name: String::from("Buddy") };
    
    println!("{} says: {}", my_dog.name, my_dog.sound());
    // Output: Buddy says: Woof!
}

Step 4: Multiple Types, Same Trait

The power of traits is that DIFFERENT types can share the SAME capability.

Adding More Types

trait MakesSound {
    fn sound(&self) -> String;
}

// A Dog
struct Dog {
    name: String,
}

impl MakesSound for Dog {
    fn sound(&self) -> String {
        String::from("Woof!")
    }
}

// A Cat
struct Cat {
    name: String,
}

impl MakesSound for Cat {
    fn sound(&self) -> String {
        String::from("Meow!")
    }
}

// A Car
struct Car {
    model: String,
}

impl MakesSound for Car {
    fn sound(&self) -> String {
        String::from("Vroom!")
    }
}

Using Them

fn main() {
    let dog = Dog { name: String::from("Buddy") };
    let cat = Cat { name: String::from("Whiskers") };
    let car = Car { model: String::from("Toyota") };
    
    println!("Dog says: {}", dog.sound());    // Woof!
    println!("Cat says: {}", cat.sound());    // Meow!
    println!("Car says: {}", car.sound());    // Vroom!
}

What's Happening?

Three completely different types:

But they ALL have the sound() method because they ALL implement MakesSound.

Each one implements it DIFFERENTLY:

Same method name. Same signature. Different behavior. That's the power of traits!


Step 5: A Type Can Have Multiple Traits

A type isn't limited to one trait. It can implement many!

Example

trait MakesSound {
    fn sound(&self) -> String;
}

trait HasLegs {
    fn leg_count(&self) -> u32;
}

struct Dog {
    name: String,
}

// Dog implements MakesSound
impl MakesSound for Dog {
    fn sound(&self) -> String {
        String::from("Woof!")
    }
}

// Dog ALSO implements HasLegs
impl HasLegs for Dog {
    fn leg_count(&self) -> u32 {
        4
    }
}

fn main() {
    let dog = Dog { name: String::from("Buddy") };
    
    println!("{} says: {}", dog.name, dog.sound());      // Buddy says: Woof!
    println!("{} has {} legs", dog.name, dog.leg_count()); // Buddy has 4 legs
}

Key Point

Dog now has TWO capabilities:

You can implement as many traits as you want on a type.


Step 6: Traits as Parameters: The Real Power

Here's where traits become incredibly useful.

Remember the problem? We wanted a generic function, but generics were too flexible. Now we can combine generics WITH traits.

The Scenario

We want a function that takes ANYTHING that makes a sound and prints that sound.

Without Traits (The Problem)

fn print_sound<T>(item: &T) {
    println!("Sound: {}", item.sound());  // ERROR!
}

This doesn't work because Rust doesn't know if T has a sound() method. T could be ANYTHING, even types that don't make sounds.

With Traits (The Solution)

fn print_sound<T: MakesSound>(item: &T) {
    println!("Sound: {}", item.sound());  // Works!
}

Now it works! Let's break down this new syntax.

Breaking Down <T: MakesSound>

fn print_sound<T: MakesSound>(item: &T)
//            ^^^^^^^^^^^^^^^
//            This is called a TRAIT BOUND
fn print_sound<T: MakesSound>(item: &T)
//             ^
//             T is a type parameter (generic)
fn print_sound<T: MakesSound>(item: &T)
//              ^
//              The colon means "where T must implement..."
fn print_sound<T: MakesSound>(item: &T)
//               ^^^^^^^^^^
//               The trait that T must implement

What This Means

<T: MakesSound> says:

"T can be any type, BUT it must implement the MakesSound trait."

This is called a trait bound. It BOUNDS (limits) what types T can be.

Full Example

trait MakesSound {
    fn sound(&self) -> String;
}

struct Dog {
    name: String,
}

impl MakesSound for Dog {
    fn sound(&self) -> String {
        String::from("Woof!")
    }
}

struct Cat {
    name: String,
}

impl MakesSound for Cat {
    fn sound(&self) -> String {
        String::from("Meow!")
    }
}

struct Rock {}  // Rock doesn't implement MakesSound!

// This function accepts ANY type that implements MakesSound
fn print_sound<T: MakesSound>(item: &T) {
    println!("Sound: {}", item.sound());
}

fn main() {
    let dog = Dog { name: String::from("Buddy") };
    let cat = Cat { name: String::from("Whiskers") };
    let rock = Rock {};
    
    print_sound(&dog);  // Works! Dog implements MakesSound
    print_sound(&cat);  // Works! Cat implements MakesSound
    
    // print_sound(&rock);  // ERROR! Rock doesn't implement MakesSound
}

The Error You'd Get

If you tried print_sound(&rock):

error: the trait bound `Rock: MakesSound` is not satisfied

Rust catches this at COMPILE TIME. You can't even run the program with this bug.


Step 7: Alternative Syntax: impl Trait

There's a shorter way to write trait bounds for function parameters.

The Long Way (Trait Bound)

fn print_sound<T: MakesSound>(item: &T) {
    println!("Sound: {}", item.sound());
}

The Short Way (impl Trait)

fn print_sound(item: &impl MakesSound) {
    println!("Sound: {}", item.sound());
}

Breaking Down impl Trait

fn print_sound(item: &impl MakesSound)
//                    ^^^^
//                    Keyword
fn print_sound(item: &impl MakesSound)
//                        ^^^^^^^^^^
//                        The trait it must implement

Both Are Equivalent

These two are the same:

// Long way
fn print_sound<T: MakesSound>(item: &T) { ... }

// Short way
fn print_sound(item: &impl MakesSound) { ... }

When to Use Which?

Use impl Trait (short way) when:

Use trait bounds (long way) when:

Example: When You Need the Long Way

// This ensures BOTH items are the SAME type
fn compare_sounds<T: MakesSound>(item1: &T, item2: &T) {
    println!("Item 1: {}", item1.sound());
    println!("Item 2: {}", item2.sound());
}

With impl Trait, you CAN'T guarantee they're the same type:

// These could be DIFFERENT types (both just need MakesSound)
fn compare_sounds(item1: &impl MakesSound, item2: &impl MakesSound) {
    println!("Item 1: {}", item1.sound());
    println!("Item 2: {}", item2.sound());
}

Step 8: Multiple Trait Bounds

What if you need a type that implements MORE than one trait?

The Scenario

We want a function that:

  1. Prints the sound something makes (MakesSound)
  2. Prints how many legs it has (HasLegs)

The type needs BOTH traits.

Using + to Combine Traits

fn describe<T: MakesSound + HasLegs>(item: &T) {
    println!("Sound: {}", item.sound());
    println!("Legs: {}", item.leg_count());
}

Breaking It Down

fn describe<T: MakesSound + HasLegs>(item: &T)
//            ^^^^^^^^^^
//            Must implement MakesSound
fn describe<T: MakesSound + HasLegs>(item: &T)
//                       ^
//                       AND (the + sign)
fn describe<T: MakesSound + HasLegs>(item: &T)
//                         ^^^^^^^
//                         Must ALSO implement HasLegs

Full Example

trait MakesSound {
    fn sound(&self) -> String;
}

trait HasLegs {
    fn leg_count(&self) -> u32;
}

struct Dog {
    name: String,
}

impl MakesSound for Dog {
    fn sound(&self) -> String {
        String::from("Woof!")
    }
}

impl HasLegs for Dog {
    fn leg_count(&self) -> u32 {
        4
    }
}

struct Car {
    model: String,
}

impl MakesSound for Car {
    fn sound(&self) -> String {
        String::from("Vroom!")
    }
}
// Note: Car does NOT implement HasLegs!

fn describe<T: MakesSound + HasLegs>(item: &T) {
    println!("Sound: {}", item.sound());
    println!("Legs: {}", item.leg_count());
}

fn main() {
    let dog = Dog { name: String::from("Buddy") };
    let car = Car { model: String::from("Toyota") };
    
    describe(&dog);  // Works! Dog has BOTH traits
    
    // describe(&car);  // ERROR! Car only has MakesSound, not HasLegs
}

You Can Require Many Traits

fn do_stuff<T: TraitA + TraitB + TraitC + TraitD>(item: &T) {
    // T must implement ALL of these traits
}

Step 9: The where Clause: Cleaner Syntax

When you have many trait bounds, the function signature gets messy:

fn some_function<T: MakesSound + HasLegs, U: MakesSound + Clone>(t: &T, u: &U) -> i32 {
    // Hard to read!
}

The where clause makes this cleaner.

Using where

fn some_function<T, U>(t: &T, u: &U) -> i32
where
    T: MakesSound + HasLegs,
    U: MakesSound + Clone,
{
    // Much cleaner!
}

Breaking It Down

fn some_function<T, U>(t: &T, u: &U) -> i32
//              ^^^^^^
//              Just declare the type parameters, no bounds here
fn some_function<T, U>(t: &T, u: &U) -> i32
where
//^^^
//Keyword that starts the bounds section
fn some_function<T, U>(t: &T, u: &U) -> i32
where
    T: MakesSound + HasLegs,
//  ^^^^^^^^^^^^^^^^^^^^^^^
//  Bounds for T
    U: MakesSound + Clone,
//  ^^^^^^^^^^^^^^^^^^^^^
//  Bounds for U
{
    // function body
}

When to Use where

Both styles work the same. where is just for readability.


Step 10: Default Implementations

Sometimes you want a trait method that has a default behavior.

The Problem

Every type that implements a trait must provide ALL the methods. But sometimes you want a sensible default that most types can use.

Example: A Trait With a Default

trait Describable {
    fn describe(&self) -> String {
        String::from("This is something.")
    }
}

Breaking It Down

trait Describable {
    fn describe(&self) -> String {
//                              ^
//                              Note: NO semicolon!
        String::from("This is something.")
//      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//      This is the DEFAULT implementation
    }
}

When a method has a body (not just a semicolon), that's the DEFAULT implementation.

Using the Default

trait Describable {
    fn describe(&self) -> String {
        String::from("This is something.")
    }
}

struct Mystery {}

// Implement the trait but DON'T provide describe
impl Describable for Mystery {}
//                          ^^
//                          Empty impl block!

fn main() {
    let m = Mystery {};
    println!("{}", m.describe());  // "This is something."
    // Uses the default!
}

Overriding the Default

Types can still provide their own implementation:

trait Describable {
    fn describe(&self) -> String {
        String::from("This is something.")
    }
}

struct Dog {
    name: String,
}

// Override the default
impl Describable for Dog {
    fn describe(&self) -> String {
        format!("A dog named {}", self.name)
    }
}

struct Mystery {}

// Use the default
impl Describable for Mystery {}

fn main() {
    let dog = Dog { name: String::from("Buddy") };
    let mystery = Mystery {};
    
    println!("{}", dog.describe());      // "A dog named Buddy" (custom)
    println!("{}", mystery.describe());  // "This is something." (default)
}

Step 11: Default Methods Can Call Other Trait Methods

A default method can call other methods from the same trait, even if those methods don't have defaults!

Example

trait Describable {
    // This method has NO default, types MUST implement it
    fn name(&self) -> String;
    
    // This method HAS a default, it calls name()
    fn describe(&self) -> String {
        format!("This is called: {}", self.name())
//                                    ^^^^^^^^^^^
//                                    Calls the other trait method!
    }
}

Using It

trait Describable {
    fn name(&self) -> String;  // Required
    
    fn describe(&self) -> String {  // Has default
        format!("This is called: {}", self.name())
    }
}

struct Dog {
    dog_name: String,
}

impl Describable for Dog {
    // We MUST implement name() because it has no default
    fn name(&self) -> String {
        self.dog_name.clone()
    }
    
    // We DON'T have to implement describe(), we get it for free!
}

fn main() {
    let dog = Dog { dog_name: String::from("Buddy") };
    
    println!("{}", dog.name());      // "Buddy"
    println!("{}", dog.describe());  // "This is called: Buddy"
}

Key Point


Step 12: Returning Types That Implement a Trait

You can use impl Trait in return position too!

Example

trait MakesSound {
    fn sound(&self) -> String;
}

struct Dog {
    name: String,
}

impl MakesSound for Dog {
    fn sound(&self) -> String {
        String::from("Woof!")
    }
}

fn create_noisy_thing() -> impl MakesSound {
//                         ^^^^^^^^^^^^^^^
//                         Returns SOMETHING that implements MakesSound
    Dog { name: String::from("Buddy") }
}

Breaking It Down

fn create_noisy_thing() -> impl MakesSound {
//                         ^^^^
//                         Keyword
fn create_noisy_thing() -> impl MakesSound {
//                              ^^^^^^^^^^
//                              The trait it implements

What This Means

The function says:

"I return something that implements MakesSound. You can call sound() on it. But I'm not telling you the exact type."

Using It

fn main() {
    let thing = create_noisy_thing();
    
    println!("{}", thing.sound());  // "Woof!"
    
    // We can use any MakesSound methods
    // But we can't use Dog-specific methods (we don't know it's a Dog)
}

Important Limitation!

You can only return ONE concrete type. This does NOT work:

fn create_noisy_thing(use_dog: bool) -> impl MakesSound {
    if use_dog {
        Dog { name: String::from("Buddy") }
    } else {
        Cat { name: String::from("Whiskers") }  // ERROR!
    }
}

Even though both Dog and Cat implement MakesSound, Rust needs to know the EXACT type at compile time. The function can only return ONE type.


Step 13: Conditionally Implementing Methods

You can implement methods on a generic struct ONLY when the type parameter has certain traits.

Example

struct Wrapper<T> {
    value: T,
}

// Methods for ALL Wrapper<T>
impl<T> Wrapper<T> {
    fn new(value: T) -> Wrapper<T> {
        Wrapper { value }
    }
}

// Methods ONLY when T implements MakesSound
impl<T: MakesSound> Wrapper<T> {
    fn play_sound(&self) {
        println!("Sound: {}", self.value.sound());
    }
}

Breaking Down the Conditional impl

impl<T: MakesSound> Wrapper<T> {
//  ^^^^^^^^^^^^^^
//  T must implement MakesSound

This means play_sound only exists when T implements MakesSound.

Full Example

trait MakesSound {
    fn sound(&self) -> String;
}

struct Dog {
    name: String,
}

impl MakesSound for Dog {
    fn sound(&self) -> String {
        String::from("Woof!")
    }
}

struct Rock {}  // No MakesSound for Rock!

struct Wrapper<T> {
    value: T,
}

impl<T> Wrapper<T> {
    fn new(value: T) -> Wrapper<T> {
        Wrapper { value }
    }
}

impl<T: MakesSound> Wrapper<T> {
    fn play_sound(&self) {
        println!("Sound: {}", self.value.sound());
    }
}

fn main() {
    let dog_wrapper = Wrapper::new(Dog { name: String::from("Buddy") });
    let rock_wrapper = Wrapper::new(Rock {});
    
    dog_wrapper.play_sound();  // Works! Dog implements MakesSound
    
    // rock_wrapper.play_sound();  // ERROR! Rock doesn't implement MakesSound
}

Key Point

The method just doesn't exist for types that don't have the trait. Compile-time safety!


Step 14: Solving the Original Problem!

Remember where we started? The largest function that wouldn't compile?

fn largest<T>(list: &[T]) -> &T {
    let mut largest = &list[0];
    for item in list {
        if item > largest {  // ERROR: can't compare T
            largest = item;
        }
    }
    largest
}

Now we can fix it with trait bounds!

The Fix

fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];
    for item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

What's PartialOrd?

PartialOrd is a trait from Rust's standard library. It means "this type can be compared with <, >, <=, >=."

Types that implement PartialOrd:

Types that DON'T implement PartialOrd:

Using Our Fixed Function

fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];
    for item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

fn main() {
    let numbers = [1, 5, 3, 9, 2];
    let biggest = largest(&numbers);
    println!("Largest number: {}", biggest);  // 9
    
    let chars = ['a', 'z', 'm', 'b'];
    let biggest_char = largest(&chars);
    println!("Largest char: {}", biggest_char);  // z
}

It works! The function accepts any type that can be compared.


Step 15: Common Traits from the Standard Library

Rust has many built-in traits. Here are some you'll see often:

PartialOrd: Can be compared with <, >, <=, >=

fn find_max<T: PartialOrd>(a: T, b: T) -> T {
    if a > b { a } else { b }
}

Clone: Can be cloned (duplicated)

fn duplicate<T: Clone>(item: &T) -> T {
    item.clone()
}

Copy: Can be copied implicitly (like integers)

fn double<T: Copy>(item: T) -> (T, T) {
    (item, item)  // Uses item twice, needs Copy
}

Debug: Can be printed with {:?}

fn print_debug<T: std::fmt::Debug>(item: &T) {
    println!("{:?}", item);
}

Display: Can be printed with {}

fn print_nice<T: std::fmt::Display>(item: &T) {
    println!("{}", item);
}

Combining Them

fn do_stuff<T: Clone + PartialOrd + Debug>(item: &T) {
    let copy = item.clone();
    println!("Item: {:?}", item);
    // Can also compare with < >
}

Step 16: Quick Reference

Define a Trait

trait TraitName {
    fn required_method(&self) -> ReturnType;
    
    fn default_method(&self) -> ReturnType {
        // default implementation
    }
}

Implement a Trait

impl TraitName for MyType {
    fn required_method(&self) -> ReturnType {
        // implementation
    }
}

Trait Bound (Long Way)

fn function<T: TraitName>(item: &T) { }

Trait Bound (Short Way)

fn function(item: &impl TraitName) { }

Multiple Bounds

fn function<T: TraitA + TraitB>(item: &T) { }

Where Clause

fn function<T, U>(t: &T, u: &U)
where
    T: TraitA + TraitB,
    U: TraitC,
{ }

Return impl Trait

fn function() -> impl TraitName { }

Conditional Implementation

impl<T: SomeTrait> MyStruct<T> {
    fn special_method(&self) { }
}

Summary: What We Learned About Traits

  1. Traits define capabilities: what a type can DO

  2. Syntax: trait Name { fn method(&self); }

  3. Implement with: impl Trait for Type { ... }

  4. Different types, same trait: many types can share a capability

  5. Trait bounds limit generics: <T: Trait> means "T must have this capability"

  6. Two syntax options:

    • Long: fn foo<T: Trait>(x: &T)
    • Short: fn foo(x: &impl Trait)
  7. Multiple bounds with +: T: TraitA + TraitB

  8. where clause: cleaner syntax for complex bounds

  9. Default implementations: provide fallback behavior

  10. Return impl Trait: return something that has a capability

  11. Conditional methods: methods that only exist when bounds are met

  12. Standard library traits: Clone, Copy, PartialOrd, Debug, etc.


Traits Exercises


Exercise 1: Define a Trait

Define a trait called Speak with one method:

trait Speak {
    fn speak(&self) -> String;
}

Create a struct Dog (no fields needed).

Implement Speak for Dog so it returns "Woof!".

Test:

let d = Dog {};
println!("{}", d.speak());

Expected output:

Woof!

Exercise 2: Same Trait, Different Types

Using Speak from Exercise 1, add:

Test:

let d = Dog {};
let c = Cat {};
println!("{}", d.speak());
println!("{}", c.speak());

Expected output:

Woof!
Meow!

Exercise 3: Trait with Data

Create a struct Person with a name: String field.

Implement Speak for Person returning "Hi, I'm <name>!".

Test:

let p = Person { name: String::from("Alice") };
println!("{}", p.speak());

Expected output:

Hi, I'm Alice!

Exercise 4: Function with Trait Bound

Write this function:

fn make_speak<T: Speak>(thing: &T) {
    println!("Says: {}", thing.speak());
}

Call it with a Dog, Cat, and Person.

Expected output:

Says: Woof!
Says: Meow!
Says: Hi, I'm Alice!

Exercise 5: Alternative Syntax

Rewrite Exercise 4's function using impl Trait:

fn make_speak(thing: &impl Speak) {
    println!("Says: {}", thing.speak());
}

Verify it works the same way.


Exercise 6: Default Implementation

Create a new trait with a default method:

trait Greet {
    fn greet(&self) -> String {
        String::from("Hello!")
    }
}

Create two structs:

Test:

let f = Friendly {};
let s = Shy {};
println!("{}", f.greet());
println!("{}", s.greet());

Expected output:

Hello!
...hi

Hint: To use default, just write impl Greet for Friendly {}


Exercise 7: Two Traits

Define two simple traits:

trait CanWalk {
    fn walk(&self) -> String;
}

trait CanSwim {
    fn swim(&self) -> String;
}

Create a struct Duck.

Implement BOTH traits for Duck:

Test:

let d = Duck {};
println!("{}", d.walk());
println!("{}", d.swim());

Expected output:

Duck walking
Duck swimming

Exercise 8: Multiple Trait Bounds

Write a function that needs BOTH traits from Exercise 7:

fn do_both<T: CanWalk + CanSwim>(thing: &T) {
    println!("{}", thing.walk());
    println!("{}", thing.swim());
}

Call it with Duck.

Expected output:

Duck walking
Duck swimming

Exercise 9: Using Standard Library Trait

Rust has a built-in trait PartialOrd for comparing with >.

Write this function:

fn is_bigger<T: PartialOrd>(a: T, b: T) -> bool {
    a > b
}

Test:

println!("{}", is_bigger(10, 5));
println!("{}", is_bigger(3, 8));

Expected output:

true
false

Exercise 10: Trait Bound on Struct Method

Create:

struct Holder<T> {
    value: T,
}

impl<T> Holder<T> {
    fn new(value: T) -> Holder<T> {
        Holder { value }
    }
}

Add a show method that only works when T can be printed:

impl<T: std::fmt::Display> Holder<T> {
    fn show(&self) {
        println!("{}", self.value);
    }
}

Test:

let h = Holder::new(42);
h.show();

Expected output:

42