Advanced Type Features
Sized
std::marker::Sized is the compiler-implemented marker trait for types with a constant byte size known at compile time, and ?Sized ("may or may not be Sized") is the relaxed bound syntax that opts a generic type parameter out of Rust's implicit T: Sized default.
Implicit Sized Bounds Across Rust Constructs
To keep everyday generic code ergonomic, Rust automatically adds an implicit Sized bound to almost every generic type parameter—with one important exception (trait Self):
| Construct | Written Syntax | Implicit Desugared Bound | Why |
|---|---|---|---|
| Generic function | fn foo<T>(x: &T) |
fn foo<T: Sized>(x: &T) |
Allows local bindings and by-value returns of T by default |
Generic struct / enum |
struct Box<T> |
struct Box<T: Sized> |
Struct fields must have known layout unless opted out on the tail field |
Generic impl block |
impl<T> Wrapper<T> |
impl<T: Sized> Wrapper<T> |
Matches default struct bound |
trait definition Self |
trait MyTrait { ... } |
trait MyTrait where Self: ?Sized |
No implicit Sized bound! If Self: Sized were added implicitly to traits, dyn MyTrait could never implement MyTrait! |
Implicit Bound Comparison:
fn process<T>(val: &T) ===> `T: Sized` is added implicitly!
(Rejects `process("hello")` where `T = str`!)
fn process<T: ?Sized>(val: &T) => `T` can be `Sized` OR unsized (`str`, `[U]`, `dyn Trait`)!
Widening Generic Functions with T: ?Sized
Whenever a generic function or struct only accesses T behind a pointer (&T, &mut T, Box<T>, Rc<T>, Arc<T>), adding T: ?Sized makes the API strictly more general without costing any performance on Sized types:
use std::fmt::Debug;
// Without `?Sized`, passing `"slice"` (`&str`) or `&[1, 2][..]` (`&[i32]`)
// would fail to compile because `str` and `[i32]` are `!Sized`:
fn log_inspected<T: Debug + ?Sized>(label: &str, value: &T) {
println!(
"[{label}] value = {:?} (size_of_val = {} bytes)",
value,
size_of_val(value)
);
}
fn main() {
let sized_num: u64 = 42;
let unsized_str: &str = "dynamic string slice";
let unsized_slice: &[i32] = &[10, 20, 30];
let trait_obj: &dyn Debug = &("tuple", 99);
log_inspected("u64", &sized_num);
log_inspected("str", unsized_str);
log_inspected("[i32]", unsized_slice);
log_inspected("dyn Debug", trait_obj);
}
[u64] value = 42 (size_of_val = 8 bytes)
[str] value = "dynamic string slice" (size_of_val = 20 bytes)
[[i32]] value = [10, 20, 30] (size_of_val = 12 bytes)
[dyn Debug] value = ("tuple", 99) (size_of_val = 24 bytes)
Requiring Self: Sized on Specific Trait Methods vs. the Entire Trait
trait Parse: Sized: Putting: Sizedon the trait header requires every implementor to beSized(for example, when methods returnResult<Self, Error>by value) and permanently disablesdyn Parse.fn into_boxed(self) -> Box<Self> where Self: Sized: Puttingwhere Self: Sizedon an individual trait method allows that method to take or returnSelfby value while keeping the rest of the trait usable asdyn Trait!