% Unsized Types
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:
- We can only manipulate an instance of an unsized type via a pointer. An
&[T]
works fine, but a[T]
does not. - Variables and arguments cannot have dynamically sized types.
- Only the last field in a
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
impl<T> Foo for [T] {
Instead, you would have to write:
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
.
?Sized
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.
commit 6ba9520