In this lesson, you will learn how to choose between being overly restrictive and perfectly flexible when setting trait bounds on your generic functions.
The Balancing Act
When you write a generic function, you have to decide which traits the input type must satisfy. If you are too strict, your function becomes hard to use. If you are too flexible, you might lack the necessary features to implement your logic.
The Problem with Strictness
Imagine you want to write a function that calculates the sum of a list. You might think, "I need to add these numbers, so I will require the Add trait."
fn sum_list<T: Add<Output = T> + Copy>(items: &[T]) -> T {
// ...
}
This is strict. By requiring Copy, you prevent the user from passing in types that don't support copying (like large structs or heap-allocated data), even if your logic didn't strictly require a bitwise copy to function. You have artificially narrowed your audience.
The Case for Flexibility
To be more flexible, you should request the minimum set of traits needed to get the job done. If your function only needs to read data, don't ask for Clone. If it only needs to iterate, don't ask for Vec or Slice.
Consider this alternative approach using references:
fn print_anything<T: std::fmt::Display>(item: &T) {
println!("{}", item);
}
By accepting &T (a reference) instead of T (the owned type), you make your function significantly more flexible. The caller doesn't have to worry about ownership or whether their type implements Clone. They just pass a reference, and your code works for almost anything that can be displayed.
Finding the Sweet Spot
Ask yourself: "What is the smallest set of capabilities I need to perform this task?"
- Too Strict: Requiring
Clonewhen you could have used a reference. - Too Flexible: Omitting a trait you actually need, leading to confusing compiler errors inside your function body.
Always start with the most permissive bounds possible. If you find your logic needs an extra feature later, you can always add another trait bound, but removing a trait bound later might break code that users have already written for your function.
Key Takeaway: Design your trait bounds to be as permissive as possible by requesting only the absolute minimum set of capabilities required to fulfill your function's purpose.