]>
Commit | Line | Data |
---|---|---|
8bb4bdeb | 1 | # Exotically Sized Types |
c1a9b12d | 2 | |
450edc1f XL |
3 | Most of the time, we expect types to have a statically known and positive size. |
4 | This isn't always the case in Rust. | |
c1a9b12d | 5 | |
136023e0 | 6 | ## Dynamically Sized Types (DSTs) |
c1a9b12d | 7 | |
450edc1f | 8 | Rust supports Dynamically Sized Types (DSTs): types without a statically |
c1a9b12d SL |
9 | known size or alignment. On the surface, this is a bit nonsensical: Rust *must* |
10 | know the size and alignment of something in order to correctly work with it! In | |
450edc1f XL |
11 | this regard, DSTs are not normal types. Because they lack a statically known |
12 | size, these types can only exist behind a pointer. Any pointer to a | |
13 | DST consequently becomes a *wide* pointer consisting of the pointer and the | |
c1a9b12d SL |
14 | information that "completes" them (more on this below). |
15 | ||
450edc1f XL |
16 | There are two major DSTs exposed by the language: |
17 | ||
18 | * trait objects: `dyn MyTrait` | |
29967ef6 | 19 | * slices: [`[T]`][slice], [`str`], and others |
c1a9b12d SL |
20 | |
21 | A trait object represents some type that implements the traits it specifies. | |
b039eaaf | 22 | The exact original type is *erased* in favor of runtime reflection |
c1a9b12d | 23 | with a vtable containing all the information necessary to use the type. |
450edc1f XL |
24 | The information that completes a trait object pointer is the vtable pointer. |
25 | The runtime size of the pointee can be dynamically requested from the vtable. | |
c1a9b12d SL |
26 | |
27 | A slice is simply a view into some contiguous storage -- typically an array or | |
450edc1f XL |
28 | `Vec`. The information that completes a slice pointer is just the number of elements |
29 | it points to. The runtime size of the pointee is just the statically known size | |
30 | of an element multiplied by the number of elements. | |
c1a9b12d SL |
31 | |
32 | Structs can actually store a single DST directly as their last field, but this | |
33 | makes them a DST as well: | |
34 | ||
35 | ```rust | |
36 | // Can't be stored on the stack directly | |
450edc1f | 37 | struct MySuperSlice { |
c1a9b12d SL |
38 | info: u32, |
39 | data: [u8], | |
40 | } | |
41 | ``` | |
42 | ||
450edc1f XL |
43 | Although such a type is largely useless without a way to construct it. Currently the |
44 | only properly supported way to create a custom DST is by making your type generic | |
45 | and performing an *unsizing coercion*: | |
46 | ||
47 | ```rust | |
48 | struct MySuperSliceable<T: ?Sized> { | |
49 | info: u32, | |
e1599b0c | 50 | data: T, |
450edc1f XL |
51 | } |
52 | ||
53 | fn main() { | |
54 | let sized: MySuperSliceable<[u8; 8]> = MySuperSliceable { | |
55 | info: 17, | |
56 | data: [0; 8], | |
57 | }; | |
58 | ||
59 | let dynamic: &MySuperSliceable<[u8]> = &sized; | |
60 | ||
61 | // prints: "17 [0, 0, 0, 0, 0, 0, 0, 0]" | |
62 | println!("{} {:?}", dynamic.info, &dynamic.data); | |
63 | } | |
64 | ``` | |
65 | ||
66 | (Yes, custom DSTs are a largely half-baked feature for now.) | |
67 | ||
136023e0 | 68 | ## Zero Sized Types (ZSTs) |
c1a9b12d | 69 | |
450edc1f | 70 | Rust also allows types to be specified that occupy no space: |
c1a9b12d SL |
71 | |
72 | ```rust | |
450edc1f | 73 | struct Nothing; // No fields = no size |
c1a9b12d SL |
74 | |
75 | // All fields have no size = no size | |
450edc1f XL |
76 | struct LotsOfNothing { |
77 | foo: Nothing, | |
c1a9b12d SL |
78 | qux: (), // empty tuple has no size |
79 | baz: [u8; 0], // empty array has no size | |
80 | } | |
81 | ``` | |
82 | ||
83 | On their own, Zero Sized Types (ZSTs) are, for obvious reasons, pretty useless. | |
84 | However as with many curious layout choices in Rust, their potential is realized | |
450edc1f XL |
85 | in a generic context: Rust largely understands that any operation that produces |
86 | or stores a ZST can be reduced to a no-op. First off, storing it doesn't even | |
87 | make sense -- it doesn't occupy any space. Also there's only one value of that | |
88 | type, so anything that loads it can just produce it from the aether -- which is | |
c1a9b12d SL |
89 | also a no-op since it doesn't occupy any space. |
90 | ||
450edc1f | 91 | One of the most extreme examples of this is Sets and Maps. Given a |
c1a9b12d SL |
92 | `Map<Key, Value>`, it is common to implement a `Set<Key>` as just a thin wrapper |
93 | around `Map<Key, UselessJunk>`. In many languages, this would necessitate | |
94 | allocating space for UselessJunk and doing work to store and load UselessJunk | |
95 | only to discard it. Proving this unnecessary would be a difficult analysis for | |
96 | the compiler. | |
97 | ||
98 | However in Rust, we can just say that `Set<Key> = Map<Key, ()>`. Now Rust | |
99 | statically knows that every load and store is useless, and no allocation has any | |
100 | size. The result is that the monomorphized code is basically a custom | |
101 | implementation of a HashSet with none of the overhead that HashMap would have to | |
102 | support values. | |
103 | ||
104 | Safe code need not worry about ZSTs, but *unsafe* code must be careful about the | |
105 | consequence of types with no size. In particular, pointer offsets are no-ops, | |
e1599b0c | 106 | and allocators typically [require a non-zero size][alloc]. |
c1a9b12d | 107 | |
e1599b0c XL |
108 | Note that references to ZSTs (including empty slices), just like all other |
109 | references, must be non-null and suitably aligned. Dereferencing a null or | |
110 | unaligned pointer to a ZST is [undefined behavior][ub], just like for any other | |
111 | type. | |
c1a9b12d | 112 | |
5869c6ff | 113 | [alloc]: ../std/alloc/trait.GlobalAlloc.html#tymethod.alloc |
e1599b0c | 114 | [ub]: what-unsafe-does.html |
c1a9b12d | 115 | |
136023e0 | 116 | ## Empty Types |
c1a9b12d SL |
117 | |
118 | Rust also enables types to be declared that *cannot even be instantiated*. These | |
119 | types can only be talked about at the type level, and never at the value level. | |
120 | Empty types can be declared by specifying an enum with no variants: | |
121 | ||
122 | ```rust | |
123 | enum Void {} // No variants = EMPTY | |
124 | ``` | |
125 | ||
126 | Empty types are even more marginal than ZSTs. The primary motivating example for | |
450edc1f | 127 | an empty type is type-level unreachability. For instance, suppose an API needs to |
c1a9b12d SL |
128 | return a Result in general, but a specific case actually is infallible. It's |
129 | actually possible to communicate this at the type level by returning a | |
130 | `Result<T, Void>`. Consumers of the API can confidently unwrap such a Result | |
131 | knowing that it's *statically impossible* for this value to be an `Err`, as | |
132 | this would require providing a value of type `Void`. | |
133 | ||
134 | In principle, Rust can do some interesting analyses and optimizations based | |
13cf67c4 XL |
135 | on this fact. For instance, `Result<T, Void>` is represented as just `T`, |
136 | because the `Err` case doesn't actually exist (strictly speaking, this is only | |
137 | an optimization that is not guaranteed, so for example transmuting one into the | |
138 | other is still UB). | |
139 | ||
140 | The following *could* also compile: | |
c1a9b12d | 141 | |
136023e0 | 142 | ```rust,compile_fail |
c1a9b12d SL |
143 | enum Void {} |
144 | ||
145 | let res: Result<u32, Void> = Ok(0); | |
146 | ||
147 | // Err doesn't exist anymore, so Ok is actually irrefutable. | |
148 | let Ok(num) = res; | |
149 | ``` | |
150 | ||
13cf67c4 | 151 | But this trick doesn't work yet. |
c1a9b12d SL |
152 | |
153 | One final subtle detail about empty types is that raw pointers to them are | |
b039eaaf | 154 | actually valid to construct, but dereferencing them is Undefined Behavior |
450edc1f XL |
155 | because that wouldn't make sense. |
156 | ||
157 | We recommend against modelling C's `void*` type with `*const Void`. | |
158 | A lot of people started doing that but quickly ran into trouble because | |
159 | Rust doesn't really have any safety guards against trying to instantiate | |
160 | empty types with unsafe code, and if you do it, it's Undefined Behaviour. | |
161 | This was especially problematic because developers had a habit of converting | |
162 | raw pointers to references and `&Void` is *also* Undefined Behaviour to | |
163 | construct. | |
164 | ||
165 | `*const ()` (or equivalent) works reasonably well for `void*`, and can be made | |
166 | into a reference without any safety problems. It still doesn't prevent you from | |
167 | trying to read or write values, but at least it compiles to a no-op instead | |
168 | of UB. | |
169 | ||
136023e0 | 170 | ## Extern Types |
450edc1f XL |
171 | |
172 | There is [an accepted RFC][extern-types] to add proper types with an unknown size, | |
173 | called *extern types*, which would let Rust developers model things like C's `void*` | |
174 | and other "declared but never defined" types more accurately. However as of | |
136023e0 XL |
175 | Rust 2018, [the feature is stuck in limbo over how `size_of_val::<MyExternType>()` |
176 | should behave][extern-types-issue]. | |
c1a9b12d | 177 | |
450edc1f | 178 | [extern-types]: https://github.com/rust-lang/rfcs/blob/master/text/1861-extern-types.md |
136023e0 | 179 | [extern-types-issue]: https://github.com/rust-lang/rust/issues/43467 |
29967ef6 XL |
180 | [`str`]: ../std/primitive.str.html |
181 | [slice]: ../std/primitive.slice.html |