In this lesson, you will learn how to define methods on structs that hold generic data, allowing your code to remain flexible while enforcing specific requirements.
When you define a struct with a type parameter, such as struct Container<T>, the struct itself is not a concrete type yet. Because of this, when you write an impl block to add methods, you must explicitly declare the type parameter in the impl line so the compiler knows which data type you are referencing.
The Syntax of Generic Methods
To define a method, use the syntax impl<T> Container<T>. This tells Rust: "For any type T that the user chooses to put into Container, here are the methods available."
struct Box<T> {
value: T,
}
impl<T> Box<T> {
fn new(value: T) -> Self {
Self { value }
}
fn peek(&self) -> &T {
&self.value
}
}
Constraining Methods
Sometimes, your struct might be generic, but a specific method might only make sense if the contained type T has certain capabilities (like being printable or comparable). In Rust, we enforce these requirements right in the impl block using trait bounds.
If you wanted a method that only works if T implements std::fmt::Display, you would define it like this:
impl<T: std::fmt::Display> Box<T> {
fn display_content(&self) {
println!("The value is: {}", self.value);
}
}
In this case, the Box<T> struct still accepts any type T upon creation. However, if you attempt to call display_content() on a Box containing a type that doesn't implement Display, the Rust compiler will stop you. This pattern allows your generic structs to be as broad as possible, while keeping specific operations safe and type-constrained. By anchoring your logic to the trait, you ensure the method only executes when the data inside the struct is capable of supporting the operation.
Key Takeaway: To define methods on a generic struct, you must declare the type parameter in the impl block, and you can further restrict those methods by adding trait bounds to that parameter.