The following code uses a struct with generic type. While its implementation is only valid for the given trait bound, the struct can be defined with or without the same bound. The struct's fields are private so no other code could create an instance anyway.
trait Trait { fn foo(&self); } struct Object<T: Trait> { value: T, } impl<T: Trait> Object<T> { fn bar(object: Object<T>) { object.value.foo(); } }
Should the trait bound for the structure should be omitted to conform to the DRY principle, or should it be given to clarify the dependency? Or are there circumstances one solution should be preferred over the other?
A trait defines functionality a particular type has and can share with other types. We can use traits to define shared behavior in an abstract way. We can use trait bounds to specify that a generic type can be any type that has certain behavior.
A trait in Rust is a group of methods that are defined for a particular type. Traits are an abstract definition of shared behavior amongst different types. So, in a way, traits are to Rust what interfaces are to Java or abstract classes are to C++. A trait method is able to access other methods within that trait.
The impl keyword is primarily used to define implementations on types. Inherent implementations are standalone, while trait implementations are used to implement traits for types, or other traits. Functions and consts can both be defined in an implementation.
In Rust a type is sized if its size in bytes can be determined at compile-time. Determining a type's size is important for being able to allocate enough space for instances of that type on the stack. Sized types can be passed around by value or by reference.
I believe that the existing answers are misleading. In most cases, you should not put a bound on a struct unless the struct literally will not compile without it.
I'll explain, but first, let's get one thing out of the way: this is not about reducing keystrokes. Currently in Rust you have to repeat every struct's bounds on every impl
that touches it, which is a good enough reason not to put bounds on structs right now. However, this is not my reasoning for recommending to omit trait bounds from structs. The implied_bounds
RFC will eventually be implemented, but I will still recommend not putting bounds on structs.
Bounds on structs express the wrong thing for most people. They are infectious, redundant, sometimes nearsighted, and often confusing. Even when a bound feels right, you should usually leave it off until it's proven necessary.
(In this answer, anything I say about structs applies equally to enums.)
Your data structure is special. "Object<T>
only makes sense if T
is Trait
," you say. And perhaps you are right. But the decision affects not just Object
, but any other data structure that contains an Object<T>
, even if it does not always contain an Object<T>
. Consider a programmer who wants to wrap your Object
in an enum
:
enum MyThing<T> { // error[E0277]: the trait bound `T: Trait` is not satisfied Wrapped(your::Object<T>), Plain(T), }
Within the downstream code this makes sense because MyThing::Wrapped
is only used with T
s that do implement Thing
, while Plain
can be used with any type. But if your::Object<T>
has a bound on T
, this enum
can't be compiled without that same bound, even if there are lots of uses for a Plain(T)
that don't require such a bound. Not only does this not work, but even if adding the bound doesn't make it entirely useless, it also exposes the bound in the public API of any struct that happens to use MyThing
.
Bounds on structs limit what other people can do with them. Bounds on code (impl
s and functions) do too, of course, but those constraints are (presumably) required by your own code, while bounds on structs are a preemptive strike against anyone downstream who might use your struct in an innovative way. This may be useful, but unnecessary bounds are particularly annoying for such innovators because they constrain what can compile without usefully constraining what can actually run (more on that in a moment).
So you don't think downstream innovation is possible? That doesn't mean the struct itself needs a bound. To make it impossible to construct an Object<T>
without T: Trait
, it is enough to put that bound on the impl
that contains Object
's constructor(s); if it's impossible to call a_method
on an Object<T>
without T: Trait
you can say that on the impl
that contains a_method
, or perhaps on a_method
itself. (Until implied_bounds
is implemented, you have to, anyway, so you don't even have the weak justification of "saving keystrokes." But that'll change eventually.)
Even and especially when you can't think of any way for downstream to use an un-bounded Object<T>
, you should not forbid it a priori, because...
A T: Trait
bound on Object<T>
means more than "all Object<T>
s have to have T: Trait
"; it actually means something like "the concept of Object<T>
itself does not make sense unless T: Trait
", which is a more abstract idea. Think about natural language: I've never seen a purple elephant, but I can easily name the concept of "purple elephant" despite the fact that it corresponds to no real-world animal. Types are a kind of language and it can make sense to refer to the idea of Elephant<Purple>
, even when you don't know how to create one and you certainly have no use for one. Similarly, it can make sense to express the type Object<NotTrait>
in the abstract even if you don't and can't have one in hand right now. Especially when NotTrait
is a type parameter, which may not be known in this context to implement Trait
but in some other context does.
Case study:
Cell<T>
For one example of a struct that originally had a trait bound which was eventually removed, look no farther than
Cell<T>
, which originally had aT: Copy
bound. In the RFC to remove the bound many people initially made the same kinds of arguments you may be thinking of right now, but the eventual consensus was that "Cell
requiresCopy
" was always the wrong way to think aboutCell
. The RFC was merged, paving the way for innovations likeCell::as_slice_of_cells
, which lets you do things you couldn't before in safe code, including temporarily opt-in to shared mutation. The point is thatT: Copy
was never a useful bound onCell<T>
, and it would have done no harm (and possibly some good) to leave it off from the beginning.
This kind of abstract constraint can be hard to wrap one's head around, which is probably one reason why it's so often misused. Which relates to my last point:
This does not apply to all cases of bounds on structs, but it is a common point of confusion. You may, for instance, have a struct with a type parameter that must implement a generic trait, but not know what parameter(s) the trait should take. In such cases it is tempting to use PhantomData
to add a type parameter to the main struct, but this is usually a mistake, not least because PhantomData
is hard to use correctly. Here are some examples of unnecessary parameters added because of unnecessary bounds: 1 2 3 4 5 In the majority of such cases, the correct solution is simply to remove the bound.
Okay, when do you need a bound on a struct? I can think of two reasons. In Shepmaster's answer, the struct will simply not compile without a bound, because the Iterator
implementation for I
actually defines what the struct contains; it's not just an arbitrary rule. Also, if you're writing unsafe
code and you want it to rely on a bound (T: Send
, for example), you might need to put that bound on the struct. unsafe
code is special because it can rely on invariants that are guaranteed by non-unsafe
code, so just putting the bound on the impl
that contains the unsafe
is not necessarily enough. But in all other cases, unless you really know what you're doing, you should avoid bounds on structs entirely.
It really depends on what the type is for. If it is only intended to hold values which implement the trait, then yes, it should have the trait bound e.g.
trait Child { fn name(&self); } struct School<T: Child> { pupil: T, } impl<T: Child> School<T> { fn role_call(&self) -> bool { // check everyone is here } }
In this example, only children are allowed in the school so we have the bound on the struct.
If the struct is intended to hold any value but you want to offer extra behaviour when the trait is implemented, then no, the bound shouldn't be on the struct e.g.
trait GoldCustomer { fn get_store_points(&self) -> i32; } struct Store<T> { customer: T, } impl<T: GoldCustomer> Store { fn choose_reward(customer: T) { // Do something with the store points } }
In this example, not all customers are gold customers and it doesn't make sense to have the bound on the struct.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With