Unsized Types
An unsized type (a type that does not implement std::marker::Sized, written !Sized) is a type whose byte size is not known at compile time and therefore cannot be stored directly by value in a local stack variable, passed by value as a function argument, or returned by value.
The Four Categories of Unsized Types in Rust
| Unsized Type Category | Syntax Examples | Why Its Size Is Unknown at Compile Time |
|---|---|---|
1. Slices ([T]) |
[u8], [i32], [String] |
Represents a contiguous sequence of an arbitrary runtime number of T elements |
2. String slices (str and OS/C strings) |
str, std::ffi::OsStr, std::path::Path, std::ffi::CStr |
Wrapper around [u8] of arbitrary runtime byte length |
3. Trait objects (dyn Trait) |
dyn std::io::Read, dyn std::error::Error + Send |
Erases the concrete implementing type (which could be 0 bytes or 1,024 bytes) |
| 4. Structs with an unsized tail field | struct Packet { version: u8, payload: [u8] } |
The final field is [T], str, or dyn Trait, making the struct's total size runtime-dependent |
Compile-Time Restrictions on Unsized Types (E0277)
Because the compiler must know exact stack frame offsets at compile time, every local variable binding, struct field (except the very last field), enum variant field, array element, and tuple element (except the last tuple element in internal layouts) must be Sized. Attempting to bind an unsized [i32] or str by value fails with E0277:
// ❌ does not compile
fn main() {
let raw_str: str = *"hello";
}
error[E0277]: the size for values of type `str` cannot be known at compilation time
--> src/main.rs:3:9
|
3 | let raw_str: str = *"hello";
| ^^^^^^^ doesn't have a size known at compilation time
|
= help: the trait `Sized` is not implemented for `str`
= note: all local variables must have a statically known size
Accessing Unsized Types Behind Pointers and Smart Pointers
Unsized types are always manipulated behind a pointer or reference (&T, &mut T, Box<T>, Rc<T>, Arc<T>, *const T, *mut T), which itself has a fixed, compile-time-known size of 16 bytes (a 2-word fat pointer on 64-bit platforms):
use std::rc::Rc;
fn main() {
// 1. Borrowed unsized slice and str (`&[i32]` and `&str`):
let slice_ref: &[i32] = &[10, 20, 30];
let str_ref: &str = "unsized text";
// 2. Owned heap-allocated unsized slices (`Box<[i32]>` and `Rc<str>`):
let boxed_slice: Box<[i32]> = Box::from([1, 2, 3, 4]);
let shared_str: Rc<str> = Rc::from("shared immutable text");
println!("slice_ref: {slice_ref:?}");
println!("str_ref: {str_ref}");
println!("boxed_slice: {boxed_slice:?}");
println!("shared_str: {shared_str}");
}
slice_ref: [10, 20, 30]
str_ref: unsized text
boxed_slice: [1, 2, 3, 4]
shared_str: shared immutable text