SyntaxStudy
Sign Up
Rust Trait Bounds and Static Dispatch
Rust Beginner 1 min read

Trait Bounds and Static Dispatch

Trait bounds constrain generic type parameters to types that implement specific traits. The syntax `fn process(item: T)` means `process` can be called with any type `T` that implements both `Display` and `Clone`. The compiler generates a separate, optimised copy of the function for each concrete type used — this is called monomorphisation and it enables zero-cost abstractions with no runtime overhead. The `where` clause provides an alternative syntax for trait bounds that is easier to read when there are many bounds or the signature would become too long. `where T: Display + Clone, U: Iterator` is equivalent to inline bounds. The `impl Trait` syntax in function parameters (`fn process(item: impl Display)`) is syntactic sugar for a single anonymous generic with a bound. Choosing between static dispatch (generics with trait bounds) and dynamic dispatch (`dyn Trait` trait objects) is an important design decision. Static dispatch is faster because the exact function to call is known at compile time and can be inlined. Dynamic dispatch uses a vtable pointer (fat pointer) to determine the method at runtime, enabling heterogeneous collections but adding indirection. Most Rust code prefers static dispatch.
Example
use std::fmt::{Debug, Display};

// Trait bounds with + syntax
fn print_both<T: Debug + Display>(value: T) {
    println!("Display: {value}");
    println!("Debug:   {value:?}");
}

// where clause — cleaner for multiple generics
fn compare_and_print<T, U>(a: T, b: U)
where
    T: Display + PartialOrd,
    U: Debug,
{
    println!("a={a}  b={b:?}");
}

// impl Trait in parameter position — anonymous generic
fn summarise(item: impl Display) -> String {
    format!("[{}]", item)
}

// impl Trait in return position — hides the concrete type
fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
    move |y| x + y
}

// A generic struct with a trait bound
#[derive(Debug)]
struct Wrapper<T: Display> {
    value: T,
}

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

fn main() {
    print_both(42);
    print_both("hello");

    compare_and_print(3.14f64, vec![1, 2, 3]);

    println!("{}", summarise(100));
    println!("{}", summarise("world"));

    let add5 = make_adder(5);
    println!("5 + 10 = {}", add5(10));

    let w = Wrapper { value: "rust" };
    w.show();
}