One Sized trait does not fit all
In Rust, types either possess a constant size known at compile time, or a dynamically calculated size known at run time. That is fine for most purposes, but recent proposals for the language have shown the need for a more fine-grained hierarchy. RFC 3729 from David Wood and Rémy Rakic would add a hierarchy of traits to describe types with sizes known under different circumstances. While the idea has been subject to discussion for many years, a growing number of use cases for the feature have come to light.
The need for more sizes
The two existing categories of type, colloquially called "sized" and "unsized", might seem to cover all of the possibilities. Unfortunately, some architectures are quite strange. For example, BPF programs can use "compile once — run everywhere" (CO-RE) relocations to adapt programs to different kernel versions. In such a program, the size and layout of a structure may be unknown at compile time, but effectively static at run time. This puts those structures between Rust's existing classes: accesses can't be reduced to offsets at compile time, but some of the same optimizations used for static offsets apply. Currently, Rust programs targeting BPF are simply not allowed to use CO-RE relocations — a substantial limitation that the Rust developers would like to relax if possible.
Another example comes from the vector extensions for Arm and RISC-V. These extensions define "single instruction, multiple data" (SIMD) instructions that operate over registers of a CPU-dependent size; the same instruction could correspond to 128-bit, 256-bit, or 512-bit registers depending on the specific CPU being used. Rust has support for working with these extensions through some built-in functions, but could potentially generate better code by exploiting the fact that the size of the relevant registers doesn't change at run time.
Finally, there is another proposal (RFC 1861) to add "extern types", which would be completely opaque to Rust's type system. Some C libraries have pointers to declared-but-not-defined structures that are expected to be passed back into library functions untouched; C++ libraries, which are more difficult to integrate directly into Rust code, present similar challenges. Currently, Rust programmers have a few tricks for representing these pointers in external interfaces, all of which can cause problems for the type system when used without care. Modeling opaque pointers to unknown C types as extern types would let these values be handled safely by Rust's type system — at the cost of introducing types that have no known size at all, not even at run time. These same kinds of opaque types also come up when compiling for WebAssembly or some GPU targets.
The proposal
Currently, types with a compile-time-known size are automatically made to implement the Sized trait by the compiler. All other types are assumed to have sizes dynamically calculated at run time. Wood and Rakic's proposal would add two new automatically implemented traits capturing finer distinctions. A new trait called SizeOfVal would apply to types that can have their size determined by examining pointer metadata at run time. Another, called Pointee, would apply to types that had no known size at all, and therefore could only be used through a pointer.
This hierarchy is sufficient to support extern types, but is not quite enough to represent vector-extension types or types with CO-RE relocations. To handle those, the proposal's authors suggest that it could be combined with another experimental Rust feature: const traits.
These traits allow Rust programmers to control whether methods of a trait are callable at compile time (in a "const context") or not. Objects that have a size known at compile time would be "const Sized", while those with a size that is only known at run time (but which is constant for the entire life of the program, for all values of that type) would just be "Sized". If a thing can be calculated at compile time, it can also be calculated at run time, so types that implement const Trait automatically implement Trait as well. Values that have a size that can be known at compile time, but that might be different for separate instances of a type would be "const SizeOfVal", while those that can vary from object to object and across runs of the program would be plain "SizeOfVal". Objects about which nothing is known would remain Pointee.
Rust currently allows types without a known size to be used as the last member of a structure — an analogue to C's flexible array members. The compiler does assume that the size of these members can be calculated at run time. Splitting Pointee out allows the compiler to distinguish between structures that can in principle be safely stored on the stack (SizeOfVal) and those that can't (Pointee). The rules for returning compound values on the stack are more stringent, however: the calling function needs to allocate a known amount of space for a returned value, so only Sized values can be returned in that way. Any other values need to be moved to the heap and returned behind a pointer.
Types that implement the const version of these traits are potentially eligible for more compiler optimizations: the compiler can work out exactly which offsets will be used, which makes it easier for the optimizer to split structures into individual components that can be stored in registers. Types that are Sized but not const Sized can't be handled in this way, but can at least avoid the overhead of repeatedly recalculating and storing size information by using relocations to patch the binary at run time.
To summarize, here are the different traits being discussed and their meanings:
Trait Known at compile time? Same for all values of the type? Size never changes once calculated? Optimizer-friendly? Can be non-last member in a structure? Can be used ... Example const Sized Yes Yes Yes Yes Yes Anywhere A normal Rust type Sized No Yes Yes Less so Yes Anywhere A foreign kernel type with a CO-RE relocation const SizeOfVal Yes No Yes Yes No Not returned on the stack A slice with bounds calculated in a const context SizeOfVal No No No No No Not returned on the stack A dynamically sized slice Pointee No No No No No Not on the stack A foreign C type without a fully known layout
Trait bounds
When a Rust programmer is writing generic code, they can specify which of these features a type needs to have using a bound such as "where T: SizeOfVal". Most existing code doesn't have any such bounds, however. Instead, the Rust compiler assumes that every generic type is meant to be Sized unless explicitly opted-out, since the vast majority of types do have a known size. The current syntax for opting out is "where T: ?Sized". With this proposal, that would be changed to be syntactic sugar for "where T: SizeOfVal". Since no existing types implement Pointee yet, that change is backward-compatible.
Whenever the compiler sees an explicit bound for Sized, SizeOfVal, or Pointee, it would remove the implicit Sized assumption. In the next edition of Rust, the "?Sized" syntax could be removed entirely, making the language simpler.
Next steps
Rust's RFC process involves a long period of discussion and reflection before a final call for comments. The sized trait hierarchy RFC (3729), for example, has been in discussion since November 2024, and has not yet had a final call for comments. There are a growing number of use cases for the feature, however. For example, the extern types RFC (1861) has been accepted, and it depends on adding something like Pointee to the language. Wood and Rakic helpfully include a dependency diagram showing which other in-progress RFCs depend on having a hierarchy of size traits:
rfcs#3729: Sized Hierarchy (this RFC)
│
│──→ `const Sized` Hierarchy ──→ Scalable Vectors (rfcs#3838)
│
│──→ Custom DSTs
│
│──→ Alignment traits/`DataSizeOf`/`DataAlignOf` (size != stride)
│
└──→ wasm `externref` types
It's hard to say whether this RFC will be adopted in its current state; the fine distinctions it introduces are something that the Rust community has been struggling with for years. My impression from reading the discussion is that it may reach a conclusion relatively soon, perhaps after some in-person discussion at the Rust all-hands meeting at RustWeek in May — stay tuned for LWN's coverage of that event. In any case, the growing pressure to find some resolution to the problem will likely see this proposal or something like it adopted eventually, as the language continues to accrue the features necessary for smooth cross-language interoperability.
