In this lesson, you will learn how to restrict specific methods to only work when a generic struct’s inner data type satisfies certain trait requirements.
The Problem: When Generics are "Too Generic"
When you define a struct like Container<T>, the impl block for that struct typically allows you to define methods that work for any type T. However, sometimes a method only makes sense if T can perform a specific action, such as printing itself or being compared.
If you try to use a feature that isn't guaranteed by the type T, the compiler will stop you. The solution is to apply a trait bound directly to the impl block.
Applying Bounds to impl Blocks
Instead of putting the bound on the struct definition itself (which would force every method to require that bound), you apply the bound to the specific impl block where it is needed.
Consider this example:
struct Holder<T> {
value: T,
}
// These methods work for any T
impl<T> Holder<T> {
fn new(value: T) -> Self {
Holder { value }
}
}
// These methods are only available if T implements Display
impl<T: std::fmt::Display> Holder<T> {
fn print_value(&self) {
println!("The value is: {}", self.value);
}
}
In the code above, the new method is available on a Holder<T> regardless of what T is. However, if you try to call print_value() on a Holder<T> where T does not implement Display, the Rust compiler will throw an error. This is a powerful way to keep your API clean: you provide utility methods only when the type is equipped to handle them.
Why This Matters
By placing bounds on the impl block rather than the struct, you maintain maximum flexibility. Your struct remains a generic container, but your methods become specialized tools that "unlock" only when the internal type meets your requirements. This approach ensures that your code remains safe and expressive without over-constraining the data structures themselves.
Key Takeaway: Use trait bounds on impl blocks to selectively enable methods only for generic types that satisfy specific trait requirements.