summaryrefslogtreecommitdiffstats
path: root/src/doc/nomicon/src/exotic-sizes.md
diff options
context:
space:
mode:
Diffstat (limited to 'src/doc/nomicon/src/exotic-sizes.md')
-rw-r--r--src/doc/nomicon/src/exotic-sizes.md181
1 files changed, 181 insertions, 0 deletions
diff --git a/src/doc/nomicon/src/exotic-sizes.md b/src/doc/nomicon/src/exotic-sizes.md
new file mode 100644
index 000000000..c4a6d2401
--- /dev/null
+++ b/src/doc/nomicon/src/exotic-sizes.md
@@ -0,0 +1,181 @@
+# Exotically Sized Types
+
+Most of the time, we expect types to have a statically known and positive size.
+This isn't always the case in Rust.
+
+## Dynamically Sized Types (DSTs)
+
+Rust supports Dynamically Sized Types (DSTs): types without a statically
+known size or alignment. On the surface, this is a bit nonsensical: Rust *must*
+know the size and alignment of something in order to correctly work with it! In
+this regard, DSTs are not normal types. Because they lack a statically known
+size, these types can only exist behind a pointer. Any pointer to a
+DST consequently becomes a *wide* pointer consisting of the pointer and the
+information that "completes" them (more on this below).
+
+There are two major DSTs exposed by the language:
+
+* trait objects: `dyn MyTrait`
+* slices: [`[T]`][slice], [`str`], and others
+
+A trait object represents some type that implements the traits it specifies.
+The exact original type is *erased* in favor of runtime reflection
+with a vtable containing all the information necessary to use the type.
+The information that completes a trait object pointer is the vtable pointer.
+The runtime size of the pointee can be dynamically requested from the vtable.
+
+A slice is simply a view into some contiguous storage -- typically an array or
+`Vec`. The information that completes a slice pointer is just the number of elements
+it points to. The runtime size of the pointee is just the statically known size
+of an element multiplied by the number of elements.
+
+Structs can actually store a single DST directly as their last field, but this
+makes them a DST as well:
+
+```rust
+// Can't be stored on the stack directly
+struct MySuperSlice {
+ info: u32,
+ data: [u8],
+}
+```
+
+Although such a type is largely useless without a way to construct it. Currently the
+only properly supported way to create a custom DST is by making your type generic
+and performing an *unsizing coercion*:
+
+```rust
+struct MySuperSliceable<T: ?Sized> {
+ info: u32,
+ data: T,
+}
+
+fn main() {
+ let sized: MySuperSliceable<[u8; 8]> = MySuperSliceable {
+ info: 17,
+ data: [0; 8],
+ };
+
+ let dynamic: &MySuperSliceable<[u8]> = &sized;
+
+ // prints: "17 [0, 0, 0, 0, 0, 0, 0, 0]"
+ println!("{} {:?}", dynamic.info, &dynamic.data);
+}
+```
+
+(Yes, custom DSTs are a largely half-baked feature for now.)
+
+## Zero Sized Types (ZSTs)
+
+Rust also allows types to be specified that occupy no space:
+
+```rust
+struct Nothing; // No fields = no size
+
+// All fields have no size = no size
+struct LotsOfNothing {
+ foo: Nothing,
+ qux: (), // empty tuple has no size
+ baz: [u8; 0], // empty array has no size
+}
+```
+
+On their own, Zero Sized Types (ZSTs) are, for obvious reasons, pretty useless.
+However as with many curious layout choices in Rust, their potential is realized
+in a generic context: Rust largely understands that any operation that produces
+or stores a ZST can be reduced to a no-op. First off, storing it doesn't even
+make sense -- it doesn't occupy any space. Also there's only one value of that
+type, so anything that loads it can just produce it from the aether -- which is
+also a no-op since it doesn't occupy any space.
+
+One of the most extreme examples of this is Sets and Maps. Given a
+`Map<Key, Value>`, it is common to implement a `Set<Key>` as just a thin wrapper
+around `Map<Key, UselessJunk>`. In many languages, this would necessitate
+allocating space for UselessJunk and doing work to store and load UselessJunk
+only to discard it. Proving this unnecessary would be a difficult analysis for
+the compiler.
+
+However in Rust, we can just say that `Set<Key> = Map<Key, ()>`. Now Rust
+statically knows that every load and store is useless, and no allocation has any
+size. The result is that the monomorphized code is basically a custom
+implementation of a HashSet with none of the overhead that HashMap would have to
+support values.
+
+Safe code need not worry about ZSTs, but *unsafe* code must be careful about the
+consequence of types with no size. In particular, pointer offsets are no-ops,
+and allocators typically [require a non-zero size][alloc].
+
+Note that references to ZSTs (including empty slices), just like all other
+references, must be non-null and suitably aligned. Dereferencing a null or
+unaligned pointer to a ZST is [undefined behavior][ub], just like for any other
+type.
+
+[alloc]: ../std/alloc/trait.GlobalAlloc.html#tymethod.alloc
+[ub]: what-unsafe-does.html
+
+## Empty Types
+
+Rust also enables types to be declared that *cannot even be instantiated*. These
+types can only be talked about at the type level, and never at the value level.
+Empty types can be declared by specifying an enum with no variants:
+
+```rust
+enum Void {} // No variants = EMPTY
+```
+
+Empty types are even more marginal than ZSTs. The primary motivating example for
+an empty type is type-level unreachability. For instance, suppose an API needs to
+return a Result in general, but a specific case actually is infallible. It's
+actually possible to communicate this at the type level by returning a
+`Result<T, Void>`. Consumers of the API can confidently unwrap such a Result
+knowing that it's *statically impossible* for this value to be an `Err`, as
+this would require providing a value of type `Void`.
+
+In principle, Rust can do some interesting analyses and optimizations based
+on this fact. For instance, `Result<T, Void>` is represented as just `T`,
+because the `Err` case doesn't actually exist (strictly speaking, this is only
+an optimization that is not guaranteed, so for example transmuting one into the
+other is still Undefined Behavior).
+
+The following *could* also compile:
+
+```rust,compile_fail
+enum Void {}
+
+let res: Result<u32, Void> = Ok(0);
+
+// Err doesn't exist anymore, so Ok is actually irrefutable.
+let Ok(num) = res;
+```
+
+But this trick doesn't work yet.
+
+One final subtle detail about empty types is that raw pointers to them are
+actually valid to construct, but dereferencing them is Undefined Behavior
+because that wouldn't make sense.
+
+We recommend against modelling C's `void*` type with `*const Void`.
+A lot of people started doing that but quickly ran into trouble because
+Rust doesn't really have any safety guards against trying to instantiate
+empty types with unsafe code, and if you do it, it's Undefined Behavior.
+This was especially problematic because developers had a habit of converting
+raw pointers to references and `&Void` is *also* Undefined Behavior to
+construct.
+
+`*const ()` (or equivalent) works reasonably well for `void*`, and can be made
+into a reference without any safety problems. It still doesn't prevent you from
+trying to read or write values, but at least it compiles to a no-op instead
+of Undefined Behavior.
+
+## Extern Types
+
+There is [an accepted RFC][extern-types] to add proper types with an unknown size,
+called *extern types*, which would let Rust developers model things like C's `void*`
+and other "declared but never defined" types more accurately. However as of
+Rust 2018, [the feature is stuck in limbo over how `size_of_val::<MyExternType>()`
+should behave][extern-types-issue].
+
+[extern-types]: https://github.com/rust-lang/rfcs/blob/master/text/1861-extern-types.md
+[extern-types-issue]: https://github.com/rust-lang/rust/issues/43467
+[`str`]: ../std/primitive.str.html
+[slice]: ../std/primitive.slice.html