In this lesson, you will learn how to create flexible, generic logic that automatically changes behavior based on boolean type-level predicates.
The Problem: When Generics Get Too Rigid
Sometimes, you want a function to work for any type, but with a slight adjustment based on a property of that type—for example, whether the type is "fast" or "heavy." While Rust’s type system isn't a traditional logic engine, we can use trait bounds as predicates to force the compiler to select different paths.
Using Empty Traits as Flags
The most common way to implement "predicates" is to define empty marker traits. These traits don't have methods; they simply act as labels that your types can implement.
trait IsFast {}
trait IsHeavy {}
struct SportsCar;
struct FreightTruck;
impl IsFast for SportsCar {}
impl IsHeavy for FreightTruck {}
// A generic function that branches logic via trait bounds
fn process<T>(item: T)
where
T: IsFast
{
println!("Boosting engine!");
}
Refining with Overlapping Logic
When you want to handle multiple categories, you can use these predicates to define specific implementations. By creating a wrapper trait that checks for these marker traits, you can effectively treat them as boolean checks. If a type meets the IsFast predicate, the compiler routes the call to the specialized block.
If you attempt to write a function that requires both IsFast and IsHeavy (like a T: IsFast + IsHeavy bound), the compiler effectively acts as a logical AND gate. If no type satisfies that, the code won't compile, protecting your logic from invalid states.
Why use this?
By refining bounds with these markers, you move logic out of if statements and into the type system. This means errors that would have happened at runtime (e.g., trying to perform a "fast" operation on a "heavy" truck) are caught at compile time. It turns your trait bounds into a rigorous set of requirements that strictly define what your data is allowed to do.
Key Takeaway: Marker traits act as compile-time boolean predicates, allowing you to gate generic logic so that functions only accept types that satisfy your specific functional requirements.