Trait Bounds Syntax
Part of the Object Oriented Programming section of Coddy's Rust journey — lesson 40 of 61.
So far, you've learned to define traits and implement them for structs. You've also worked with generics to write flexible code. Now it's time to combine these concepts—using traits to constrain what types a generic function can accept.
Consider a generic function that needs to call a specific method on its parameter. Without any constraints, Rust has no guarantee that the type T has that method:
fn print_info<T>(item: T) {
println!("{}", item.summarize()); // Error! T might not have summarize()
}
The solution is a trait bound. By adding : TraitName after the generic parameter, you tell Rust that T must implement that trait:
trait Summary {
fn summarize(&self) -> String;
}
fn print_info<T: Summary>(item: T) {
println!("{}", item.summarize()); // Now Rust knows this method exists
}
The syntax <T: Summary> reads as "T is any type that implements Summary." Now the function only accepts types that have the required behavior. If you try to pass a type that doesn't implement Summary, the compiler will reject it with a clear error.
This pattern is powerful because it lets you write generic code that still has access to specific functionality. Your function remains flexible—it works with any type—but only types that provide the behavior you need.
Challenge
EasyLet's build a notification system that uses trait bounds to ensure only properly formatted messages can be sent! You'll create a generic function that accepts any type implementing a specific trait, demonstrating how trait bounds constrain generic parameters.
You'll organize your code across two files:
notification.rs: Define a publicNotifytrait with a method calledmessagethat takes&selfand returns aString. Then create two public structs that implement this trait:Email— with a publicsubjectfield (String). Itsmessagemethod should returnEmail: {subject}SMS— with a publiccontentfield (String). Itsmessagemethod should returnSMS: {content}
send_notificationthat accepts any typeTimplementing theNotifytrait. This function should print the result of callingmessage()on the item.main.rs: Bring in your notification module and use the inputs provided to create both anEmailand anSMSinstance. Callsend_notificationwith each one to demonstrate that your generic function works with any type that implementsNotify.
The key insight here is that send_notification doesn't know the concrete type it receives—it only knows that the type can produce a message. The trait bound T: Notify guarantees this capability.
Your output should show both notifications being sent:
Email: {subject}
SMS: {content}For example, with inputs Meeting Tomorrow and On my way!:
Email: Meeting Tomorrow
SMS: On my way!You will receive two inputs: the email subject and the SMS content.
Cheat sheet
A trait bound constrains a generic type parameter to only accept types that implement a specific trait.
Syntax for trait bounds uses : TraitName after the generic parameter:
fn function_name<T: TraitName>(item: T) {
// Can now call methods from TraitName on item
}
Example with a trait bound:
trait Summary {
fn summarize(&self) -> String;
}
fn print_info<T: Summary>(item: T) {
println!("{}", item.summarize());
}
The notation <T: Summary> means "T is any type that implements Summary." This allows the function to call trait methods while remaining generic.
Try it yourself
mod notification;
use notification::{Email, SMS, send_notification};
fn main() {
// Read inputs
let mut subject = String::new();
std::io::stdin().read_line(&mut subject).expect("Failed to read line");
let subject = subject.trim().to_string();
let mut content = String::new();
std::io::stdin().read_line(&mut content).expect("Failed to read line");
let content = content.trim().to_string();
// TODO: Create an Email instance with the subject
// TODO: Create an SMS instance with the content
// TODO: Call send_notification with the email
// TODO: Call send_notification with the sms
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Methods and Behavior
Intro Implementation BlocksThe Self ParameterMutable MethodsAssociated FunctionsMultiple Implementation BlocksMethod ChainingRecap - Rectangle Actions4Project: Virtual Pet
Defining the PetFeeding the Pet7Standard Traits
The Debug TraitThe Display TraitClone and CopyEquality TraitsRecap - Printable Point10Project: Document System
The Draw TraitText Component2Encapsulation and Modules
Modules BasicsThe Public KeywordPrivate FieldsGettersSettersRecap - Secure Locker5Generics
Generic StructsGeneric MethodsMultiple Generic TypesGeneric FunctionsRecap - Coordinate Point8Traits as Bounds
Trait Bounds SyntaxMultiple BoundsThe Where ClauseReturning Types with TraitsRecap - Generic Printer11Design Patterns in Rust
Newtype PatternCompositionThe Drop TraitFrom and IntoRecap - Smart Pointer Mock