I can define a struct type that uses a generic type parameter with a trait bound:
struct MyStruct<T: Clone> {
field: T,
}
This prevents me me from instantiating MyStruct
with a generic type which does not meet the trait bound:
// Note: does not implement Clone
struct UnitStruct;
fn main() {
// ERROR: Unsatisfied trait bound: UnitStruct: Clone
let s = MyStruct { field: UnitStruct };
}
But why would I want to define my struct this way? What are the use cases of imposing such limitations on the instantiation of MyStruct
?
I noticed that even with the trait bound in the MyStruct
definition, if I define an interface which uses MyStruct
, I still have to repeat the trait bound:
// This works
fn func<T: Clone>(s: MyStruct<T>) -> T { s.field.clone() }
// This does not. Compiler demands a trait bound for `T`
fn func<T>(s: MyStruct<T>) -> T { s.field.clone() }
Typically you want to avoid adding trait bounds to structs when possible. When you add trait bounds this way, you will have to write out the trait bounds again on every
impl<T: A + B + C + Etc> for Foo<T>
. This is more work for you and can even reduce readability if not all of those traits are actually necessary for theimpl
block you are writing. Traits likeClone
that do not have associated types or static functions probably shouldn't be included in the type bounds unless you have a specific reason to do so.The reason you can add trait bounds this way is because it can greatly improve readability and there are some cases where it is largely unavoidable such as with associated types or fields which must be
Sized
.We can avoid this by expanding to add more type parameters, but that gets messy quickly. This can greatly reduce code readability and it gets worse if you have multiple nested generic structs.
Another very important reason is to avoid mistakes. If it doesn't make sense for a type to be in a generic field, then you can get the compiler to catch it early by enforcing it on the struct parameters. It is not a good feeling to have written your code around the idea of using a
Foo<Bar>
only to discover thatBar
was never actually a valid option.