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

Implementing Generic Traits

Write traits that can be implemented for various generic types using associated types.

In this lesson, you will learn how to make your traits flexible by defining them with generic parameters, allowing a single interface to handle various data types.

The Problem with Fixed Types

In basic traits, you define methods that take or return concrete types. For example, a Converter trait might only work with String. But what if you want a Converter that can transform an i32 into a String, or a f64 into a String? Instead of writing a new trait for every combination, we use Generic Traits.

Defining Generic Traits

When you define a trait with a generic type parameter, you allow the implementation to decide what type it works with. Here is how it looks:

trait Converter<T> {
    fn convert(&self, input: T) -> String;
}

struct NumberToString;

impl Converter<i32> for NumberToString {
    fn convert(&self, input: i32) -> String {
        input.to_string()
    }
}

In this code, Converter<T> allows NumberToString to implement the trait specifically for i32. If you later wanted to convert a f64, you could simply add another impl Converter<f64> for NumberToString.

Why Associated Types?

Sometimes, a trait should imply a specific relationship between types rather than letting the user choose the input type freely. For instance, an Iterator trait must know what type it produces. If you used a generic parameter, you could implement Iterator<String> and Iterator<i32> for the same struct, which creates ambiguity.

Instead, we use associated types to define a fixed relationship:

trait Processor {
    type Item; // Associated type
    fn process(&self) -> Self::Item;
}

struct StringProcessor;

impl Processor for StringProcessor {
    type Item = String; // The relationship is now locked in
    fn process(&self) -> String {
        "Data processed".to_string()
    }
}

By using associated types, you simplify your trait bounds. Instead of writing where T: Processor<Item>, you simply write T: Processor, making your code much cleaner while maintaining strict type safety.

Key Takeaway: Generic traits allow you to reuse interfaces across different types, while associated types help you define clear, singular relationships between a trait and the data it produces.