Sized and Sized
Every generic type parameter T in Rust carries an implicit T: Sized bound by default (fn foo<T>(x: T) means fn foo<T: Sized>(x: T)), which can be relaxed using the T: ?Sized ("maybe sized") syntax when T is only accessed behind a pointer or reference.
Why T: Sized Is the Default on All Generic Parameters
In almost all generic code, type parameters are stored by value in local variables, passed by value as parameters, or returned by value—all of which require the compiler to know size_of::<T>() at compile time. Making T: Sized the implicit default avoids forcing developers to write <T: Sized> on every single generic function and struct.
| Bound Syntax | Meaning | Types Accepted for T |
|---|---|---|
<T> (implicit T: Sized) |
T must have a compile-time-known size |
i32, String, Vec<u8>, &str (as a 16B reference), [u8; 4] |
<T: Sized> |
Explicit form of <T> (used on trait methods to exclude them from dyn Trait vtables) |
Same as <T> |
<T: ?Sized> |
T may or may not be Sized (widens bound to include DSTs) |
All Sized types plus str, [U], dyn Trait, Path, OsStr |
(Note: ?Sized is the only relaxation bound in Rust; there is no ?Clone or ?Send syntax.)
Why fn take_ref<T>(x: &T) Rejects "literal" Without ?Sized
Consider a generic function fn print_ref<T: Debug>(x: &T). If a caller passes "hello" (whose type is &'static str), matching &'static str against the parameter &T unifies T = str. Because str does not implement Sized, the call fails unless T opts into ?Sized:
use std::fmt::Debug;
// Without `?Sized`, passing `"hello"` (`&str`, where `T = str`) would fail!
// With `+ ?Sized`, `T` can be `i32` (Sized), `str` (!Sized), or `[i32]` (!Sized):
fn debug_behind_ref<T: Debug + ?Sized>(value: &T) {
println!("Debug value: {value:?} ({} bytes)", std::mem::size_of_val(value));
}
fn main() {
let number: i32 = 42;
let slice: &[i32] = &[1, 2, 3];
debug_behind_ref(&number); // T = i32 (Sized)
debug_behind_ref("rust"); // T = str (!Sized)
debug_behind_ref(slice); // T = [i32] (!Sized)
}
Debug value: 42 (4 bytes)
Debug value: "rust" (4 bytes)
Debug value: [1, 2, 3] (12 bytes)
Implicit ?Sized on Self Inside Trait Definitions
Inside a trait definition, the implicit Self type is ?Sized by default (so traits can be implemented for unsized types like str and [T] and used as dyn Trait trait objects!).
If a specific trait or trait method requires Self to be Sized (for example, taking self by value or returning Self), you add trait MyTrait: Sized or where Self: Sized explicitly:
trait ByteInspect {
// Works on both Sized types and DSTs (`&str`, `&[u8]`) because `Self: ?Sized` in traits:
fn byte_len(&self) -> usize;
// Gated with `where Self: Sized` because it consumes `self` by value:
fn into_boxed(self) -> Box<Self>
where
Self: Sized,
{
Box::new(self)
}
}
impl ByteInspect for str {
fn byte_len(&self) -> usize {
self.len()
}
}
fn main() {
println!("Byte len of 'hello': {}", "hello".byte_len());
}
Byte len of 'hello': 5