In this lesson, you will learn how to write generic functions that accept "unsized" types by explicitly opting out of Rust’s default Sized requirement.
The Implicit Bound
In Rust, whenever you write a generic function like fn process<T>(item: T), the compiler implicitly adds a trait bound: fn process<T: Sized>(item: T).
A type is Sized if the compiler knows its exact size in bytes at compile time. Most types you use—i32, structs, enums—are Sized. However, some types are "unsized" (or "dynamically sized"), such as slices [T] or trait objects dyn Trait. Because the compiler doesn't know their size, it refuses to let you pass them by value into a generic function by default.
Opting Out with ?Sized
If you want your generic function to handle both Sized types (like i32) and unsized types (like [u8]), you must tell the compiler to relax its requirements. You do this using the special ?Sized bound. The question mark acts as a "maybe" operator: "T may or may not be Sized."
fn print_anything<T: ?Sized>(item: &T) {
// We must use a reference because T might be unsized.
// We cannot move an unsized type off the stack.
}
By using T: ?Sized, you are essentially saying, "I don't require this type to have a known size." Because the compiler no longer guarantees the size, you cannot pass item by value; you must work with it via a reference (e.g., &T or &mut T), which provides the pointer metadata needed to handle the unsized data safely.
Why does this matter?
This pattern is essential when building library abstractions. If you want to write a function that works on any slice (like [u8] or [i32]) or any trait object, you cannot use the default Sized bound. Without ?Sized, your generic functions would be restricted to concrete types with known stack footprints, preventing you from writing truly polymorphic code that handles the full breadth of the Rust type system.
Key Takeaway: Use the ?Sized trait bound to opt out of the default Sized requirement, allowing your generic functions to accept unsized types via references.