Only Bounds

EvgeniyZh1 pts0 comments

Only Bounds · baby stepsNB. This page is part of the series "Revisiting Sizedness".<br>Click here to see all posts.<br>only bounds are going to be the most impactful change to Rust that you&rsquo;ve never heard of. They are currently being designed and developed by the Arm team (David Wood, Rémy Rakic, et al.) as part of the Sized Hierarchy and Scalable Vector Extension project goal. This post explores the feature and aims to answer a particular question about the design (the scope of bounds, I&rsquo;ll explain). But before I dive in, I want to give a bit of context.<br>Rust generics have a Sized bound by default today<br>In today&rsquo;s Rust, every type parameter (except for Self) has a default bound called Sized:<br>// So this function...<br>fn identityT>(t: T) -> T {

// ...is actually short for<br>fn identityT>(t: T) -> T<br>where<br>T: Sized, // {

A type T implements Sized if the compiler can compute the size of a T value at compilation time. This is true for almost every type, with a few notable exceptions. Consider [u32], which refers to &ldquo;some number of u32 instances&rdquo;. We know that a single u32 is 4 bytes, but without knowing how many u32 there are, you can&rsquo;t know the size of [u32]. This means you can&rsquo;t have a value of type [u32] on the stack (how big should the stack frame be?).<br>You opt out with ?Sized<br>However, if you have a function like by_ref, that just takes the value by reference (i.e., by pointer), you shouldn&rsquo;t need to know how big the [u32] value is, because you&rsquo;re not manipulating it directly. You can have a type parameter U that doesn&rsquo;t require Sized, but you have to explicitly &ldquo;opt out&rdquo; from the default bound:<br>fn by_refU>(t: &U)<br>where<br>U: ?Sized, // { }

As a fun bit of historical trivia, this system was introduced way back in 2014 to accommodate Dynamically Sized Types. Before that, &[u32] was actually a built-in, indivisible type; we even wrote it like [u32]/& for a time.1<br>But Sized vs ?Sized isn&rsquo;t enough for everything we need<br>The Sized vs ?Sized design has served us reasonably well but it is also showing its limits. It turns out that &ldquo;value has a statically computable size&rdquo; vs &ldquo;each value has a distinct size computable at runtime&rdquo; doesn&rsquo;t cover all the things you might want. For example, extern types are types whose values have no known size, even at runtime. And then Arm&rsquo;s Scalable Vector Extension want to describe SIMD types where every value of the type has the same size (unlike str and [T], where each value can have a different length) but where that size is not known until runtime.<br>A richer Sized hierarchy<br>Rather than just Sized or ?Sized, what we really want is to have a richer hierarchy. The current plans look something like this:<br>flowchart TD<br>subgraph S["Sizedness traits"]<br>Sized[["Sized (default)"]] -- extends --> MetadataSized<br>MetadataSized -- extends --> MaybeSized<br>end<br>where<br>trait Sized means that all values have the same size and that size can be computed knowing only the type.<br>trait MetadataSized means that values can have different sizes and that size can be computed given the metadata attached to a reference to the value. Examples include [T] or dyn Trait.<br>trait MaybeSized is implemented for all values and tells you nothing about the value&rsquo;s size.<br>Two caveats:<br>I&rsquo;m excluding the way that Arm&rsquo;s Scalable Vector Extension fit into this, because it&rsquo;s orthogonal.<br>The trait names aren&rsquo;t settled. I&rsquo;m using the names I understand the libs-api team to prefer; they&rsquo;re not my favorites, but that&rsquo;s ultimately the team who owns stdlib bikesheds, so I defer to them.2<br>Problem: ?Sized notation doesn&rsquo;t scale to this hierarchy<br>But now we have a kind of problem. The ?Sized notation was predicated3 on the idea that users should specify the default bound they are opting out of – i.e., the ? is meant to say &ldquo;I don&rsquo;t know if this is Sized or not&rdquo; (unlike the default, where you know it is Sized). But &ldquo;opting out&rdquo; from a bound doesn&rsquo;t work so well with a multi-level hierarchy. When you write ?Sized, does that correspond to T: MetadataSized (but not T: Sized)? And what if we want to insert another level in between T: MetadataSized and T: Sized later? Then we either have to change what T: ?Sized means (to refer to the new bound) or we have to have T: ?Sized drop two levels down the hierarchy. Even more annoying, what do we do while that middle rung is unstable? Surely T: ?Sized shouldn&rsquo;t refer to an unstable trait&mldr; what if we decide to remove it<br>Solution: only bounds<br>The new proposal is to write T: only MetadataSized or T: only UnknownSized instead of T: ?Sized. An only bound combines two things:<br>Like any bound, it includes a &ldquo;minimum requirement&rdquo; – i.e., T: only MetadataSized means that T must implement at least MetadataSized.<br>It additionally disables some default bounds – i.e., we will not add the default T: Sized...

sized rsquo size value bound default

Related Articles