Course contentsShow
← Rust
Lesson 683 of 1,9106. Generics and Static DispatchFree lesson

Type Constraints with Trait Bounds

Apply constraints to generic parameters to ensure they implement necessary behaviors.

In this lesson, you will learn how to restrict generic types so your functions only accept arguments that possess the specific behaviors your code requires.

The Problem with "Anything Goes"

When you define a generic function like fn print_item<T>(item: T), the compiler knows absolutely nothing about T. You cannot call methods on item because, as far as Rust knows, T could be an integer, a custom struct, or anything else that doesn't have methods. If you try to print it, the compiler will stop you: it doesn't know if T implements Display.

Applying Trait Bounds

To bridge this gap, we use trait bounds. A trait bound tells the compiler: "I will accept any type T, provided that T implements this specific trait." You apply these bounds directly in the function signature using a colon (:).

Consider this example where we want to compare two items to see if they are equal:

fn are_equal<T: PartialEq>(a: T, b: T) -> bool {
    a == b
}

By adding : PartialEq after T, we promise the compiler that we will only pass types that know how to compare themselves for equality. If you try to call are_equal with a type that hasn't implemented PartialEq, the code simply won't compile.

Why This Matters

Trait bounds are the foundation of "Static Dispatch." Because the compiler knows exactly what capabilities the type T has at compile time, it can generate optimized code specifically for that type.

You aren't just telling the compiler what to do; you are creating a contract. You define the behavior you need (like PartialEq, Display, or Clone), and the compiler enforces that contract for you, ensuring that you never accidentally pass an unsupported type into your generic functions. This keeps your generic code flexible, yet perfectly type-safe.

Key Takeaway: Trait bounds act as a gatekeeper for generics, allowing you to use common functionality on unknown types while guaranteeing at compile time that those types support the operations you need.