Most types have a particular size, in bytes, that is knowable at compile time.
For example, an i32
is thirty-two bits big, or four bytes. However, there are
some types which are useful to express, but do not have a defined size. These are
called ‘unsized’ or ‘dynamically sized’ types. One example is [T]
. This type
represents a certain number of T
in sequence. But we don’t know how many
there are, so the size is not known.
Rust understands a few of these types, but they have some restrictions. There are three:
&[T]
works fine, but a [T]
does not.struct
may have a dynamically sized type; the
other fields must not. Enum variants must not have dynamically sized types as
data.So why bother? Well, because [T]
can only be used behind a pointer, if we
didn’t have language support for unsized types, it would be impossible to write
this:
impl Foo for str {
or
fn main() { impl<T> Foo for [T] { }impl<T> Foo for [T] {
Instead, you would have to write:
fn main() { impl Foo for &str { }impl Foo for &str {
Meaning, this implementation would only work for references, and not
other types of pointers. With the impl for str
, all pointers, including (at
some point, there are some bugs to fix first) user-defined custom smart
pointers, can use this impl
.
If you want to write a function that accepts a dynamically sized type, you
can use the special bound, ?Sized
:
struct Foo<T: ?Sized> { f: T, }
This ?
, read as “T may be Sized
”, means that this bound is special: it
lets us match more kinds, not less. It’s almost like every T
implicitly has
T: Sized
, and the ?
undoes this default.