Static type systems generally require memory addresses and offsets to be resolved before execution beginns. This constraint enables robust error checking and optimization strategies. However, certain data structures, such as slices and C-style strings, do not have a predetermined length in the source code. These are classified as Dynamic Size Types (DST).
Attempting to instantiate a DST directly leads to compiler failures because the stack space cannot be reserved reliab.
Indirection Strategies
To bridge the gap between fixed-sized stack management and variable-length data, Rust utilizes smart pointers. Types like Box, Arc, Rc, and string slices &str serve as stable handles over potentially oversized targets. The compiler treats the pointer itself as a fixed-size value, regardless of the underlying data volume.
Consider the internal structure of a Box:
pub struct Box<T: ?Sized, A: Allocator = Global> {
unique: Unique<T>,
alloc: A,
}
Note the ?Sized constraint on the generic parameter T. This indicates that the box wrapper does not mandate a known size for its contents.
The Sized Trait
Implicitly, all type parameters carry the Sized bound. This ensures the type occupies a known amount of memory. To allow unconstrained types in generics, developers must explicitly use ?Sized.
// Implicitly requires Sized
fn take_item<T>(t: T) {}
// Accepts any type, sized or unsized
fn take_unsized<T: ?Sized>(t: T) {}
Runtime Polymorphism
When multiple disparate types implement the same interface, dynamic dispatch allows uniform handling. This requires a trait object, denoted by the dyn keyword. Unlike static dispatch which resolves methods at compile time via monomorphization, dynamic dispatch relies on vtables generated at runtime.
Practical Scenarios
The following implementation covers basic DST wrapping with str, size constraints, and polymorphic trait objects.
trait Entity {
fn describe(&self);
}
struct Predator {
speed: u32,
}
struct Prey {
camouflage: bool,
}
impl Entity for Predator {
fn describe(&self) {
println!("Predator active, speed: {}", self.speed);
}
}
impl Entity for Prey {
fn describe(&self) {
println!("Prey camouflaged: {}", self.camouflage);
}
}
fn handle_mixed_entities(items: Vec<Box<dyn Entity>>) {
for entity in items {
entity.describe();
}
}
fn validate_size_constraints<T: Sized>(val: T) -> T {
val
}
fn process_dynamic_strings() {
// Direct assignment to str fails
// let s: str = "data";
// Correct: Use Box to own the string slice
let owned_str: Box<str> = Box::from("Raw Data Stream");
println!("Owned buffer: {}", owned_str);
}
fn main() {
// Handling DST instances
process_dynamic_strings();
// Handling Heterogeneous Entities
let predator = Box::new(Predator { speed: 90 });
let prey = Box::new(Prey { camouflage: true });
let zoo_collection: Vec<Box<dyn Entity>> = vec![
predator as Box<dyn Entity>,
prey as Box<dyn Entity>,
];
handle_mixed_entities(zoo_collection);
// Validating Sized types specifically
let num = 5u32;
let _processed = validate_size_constraints(num);
}