In this lesson, you will learn how to use empty traits as "tags" to signal specific properties of a type to the compiler, allowing you to trigger specialized generic logic.
The Concept of Marker Traits
Sometimes, we want to write a generic function that behaves differently based on a property of the type it receives, such as whether a type is "safe to copy" or "requires encryption." By creating a trait with no methods—often called a marker trait—we can "tag" our types. Because this trait has no implementation requirements, it serves purely as a piece of metadata that the compiler uses to resolve which generic implementation to execute.
Implementing Tagged Specialization
Imagine you have a processing system that handles different data types. You want to ensure that only types marked as Sensitive undergo an encryption step.
struct UserData;
struct PublicLog;
// Define our marker traits
trait Sensitive {}
impl Sensitive for UserData {}
// A generic processor
struct Processor<T> {
data: T,
}
impl<T> Processor<T> {
fn process(&self) where T: Sensitive {
println!("Encrypting sensitive data...");
}
}
// Without the marker, the method isn't even available
impl<T> Processor<T> {
fn log(&self) {
println!("Logging generic data.");
}
}
In this example, the process method is only visible if the type T implements the Sensitive trait. If you try to call .process() on a Processor<PublicLog>, the code will fail to compile. This is a form of static dispatch that enforces domain rules at compile time rather than relying on runtime checks or if statements.
Why use tags?
- Type Safety: You prevent developers from passing the "wrong" kind of data to sensitive functions.
- Zero-Cost: Because these traits are empty and used in generic bounds, they are erased at runtime. There is no performance penalty for "tagging" your types.
- Intent: It makes your code self-documenting; looking at the
implblock immediately tells the next developer which types are permitted for that specific operation.
Key Takeaway: Marker traits act as compile-time flags that allow you to constrain generic behavior without the overhead of runtime checks or manual type inspection.