-
<T : Trait>
means that the function / trait / etc. can be "fed" / "instanced with", at compile time, any typeT
as long asT
implementsTrait
. For each differentT
, a different version of the code will be generated / copy-pasted. -
dyn Trait
, on the other hand, represents only one single concrete type, the one that unifies "all" the (other)T : Trait
. Indeed, any (slim) pointer to aT
whenT : Trait
can be coerced into a (fat) pointer to the typedyn Trait
. And the typedyn Trait
has an automagically compiler-generatedimpl Trait for dyn Trait { ... }
. This way, instead of having to deal with each and every differentT : Trait
, we just have to deal with this one type.-
For the coercion to work,
T
needs to beSized
, which is always the case with a generic type parameter unless we "remove" the: Sized
implicit bound with the?Sized
"unbound". -
If
struct Foo : Trait
,enum Bar : Trait
andunion Baz : Trait
, whereFoo
,Bar
andBaz
are fixed-size types, then<T : Trait>
is a genericity that can use any of these types, which can be coerced to dynamically sizeddyn Trait
(i.e.,dyn Trait : !Sized
). -
It is thus possible to also include
dyn Trait
generically, with the?Sized
unbound:
<T : ?Sized + Trait>
-
To better illustrate this, let's draw a parallel with slice vs. array:
-
the type
T
, where<T : Trait>
is similar to the type[u8; N]
, where<const N: usize>
: for every concrete lengthN = 0, ..., 42
, we get to copy-paste the "template" code using the concrete type[u8; 0]
,[u8; 1]
, ..., etc.[u8; 42]
, and this way, the length of the array is fixed, each time.
We get the advantage of being able to stack-allocate and/or inline things by taking advantage of the kownledge of the fixed length / size.
But we don't get to be able to feed it an array whose length is unknown at compile time: a slice, of type[u8]
.
So in some cases we'd rather have a function work on[u8]
rather than making it generic over<const N: usize> [u8; N]
. And this is okay, because as long as we have a pointer to a[u8; N]
(e.g.,&[u8; N]
,&mut [u8; N]
,Box<[u8; N]>
,Arc<[u8; N]>
, etc.), we can coerce such (slim) pointer into a (fat) pointer to a[u8]
, whereby the pointer has been enlarged with some necessary runtime metadata (in this case, the lengthn
of the slice). -
Well then this is the same with
<T : Trait>
: in some cases we'd rather have a function work ondyn Trait
rather than making it generic over<T : Trait>
. And this is okay, because as long as we have a pointer to aT
(e.g.,&T
,&mut T
,Box<T>
,Arc<T>
, etc.), we can coerce such (slim) pointer into a (fat) pointer to adyn Trait
, whereby the pointer has been enlarged with some necessary runtime metadata (in this case, a pointer to a a struct containing the size, alignment, destructor, and a bunch of function pointers corresponding to the methods defined inimpl Trait for T { ... }
).