DST - Dynamically Sized Types
Introduction
Dynamically Sized Types (DSTs) are types, primarily found in languages like Rust, whose exact size in memory is not known at compile time. This contrasts with most types which have a fixed, compile-time known size.
Compile-Time Size vs. Runtime Size
- Sized Types: Most common types (
i32,bool,f64, customstructs likePoint { x: f64, y: f64 }) have a size known by the compiler. The compiler uses this size for stack allocation, determining memory layouts, etc. These types implicitly implement theSizedmarker trait in Rust. - Dynamically Sized Types (DSTs): These types lack a fixed size based purely on compile-time information. Their actual size often depends on runtime factors.
Common Examples of DSTs (in Rust)
- Slices (
[T]): Represents a contiguous sequence of elements (T) in memory (e.g.,[u8],[String]). While the size ofTis known, the length (number of elements) of the slice is not part of the type[T]itself and is only known at runtime. - Trait Objects (
dyn Trait): Represents any concrete type that implements a specificTrait(e.g.,dyn Debug,dyn Clone). Different types implementing the same trait can have different sizes. The compiler only knows it's some type implementing the trait, not its specific size.
The Core Limitation and Solution
- Limitation: Because the compiler doesn't know their size, DSTs cannot be stored directly on the stack, passed/returned by value, or included directly within structs (unless they are the last field and the struct itself becomes a DST). The compiler needs a known size for these operations.
- Solution: DSTs must always be handled indirectly via some form of pointer. These pointers store not only the memory address but also the necessary runtime information to work with the dynamically sized data.
"Fat Pointers"
Pointers to DSTs are often called "fat pointers" because they carry extra metadata alongside the memory address:
- Pointers to Slices (e.g.,
&[T],&mut [T],Box<[T]>): Contain the starting memory address and the length (number of elements). - Pointers to Trait Objects (e.g.,
&dyn Trait,&mut dyn Trait,Box<dyn Trait>,Rc<dyn Trait>): Contain the memory address of the actual instance data and a pointer to a vtable (virtual method table). The vtable contains pointers to the specific method implementations for the underlying concrete type, allowing dynamic dispatch, and potentially includes size/alignment information.
Sized and ?Sized
Sized: In Rust, generic type parameters are implicitly bounded bySized(e.g.,<T>means<T: Sized>), meaning they must have a compile-time known size by default.?Sized: This syntax is used to opt-out of theSizedrequirement in generic contexts (read as "T may or may not be Sized"). It allows functions or types (especially pointer types likeBox<T>,Rc<T>,&T) to work with DSTs. For example, the definition ofBoxusesT: ?Sizedso that it can createBox<[u8]>orBox<dyn Debug>.
Summary
In short, DSTs are types like slices ([T]) and trait objects (dyn Trait) whose size isn't fixed at compile time. They must always be used behind a pointer (like &, Box, Rc), which becomes a "fat pointer" carrying the necessary runtime metadata (length or vtable pointer) to manage the dynamically sized data. The ?Sized trait bound is used to indicate that a generic type parameter might be a DST.