1
1
# Exotically Sized Types
2
2
3
- Most of the time, we think in terms of types with a fixed, positive size. This
4
- is not always the case, however .
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 .
5
5
6
6
7
7
8
8
9
9
10
10
# Dynamically Sized Types (DSTs)
11
11
12
- Rust in fact supports Dynamically Sized Types (DSTs): types without a statically
12
+ Rust supports Dynamically Sized Types (DSTs): types without a statically
13
13
known size or alignment. On the surface, this is a bit nonsensical: Rust * must*
14
14
know the size and alignment of something in order to correctly work with it! In
15
- this regard, DSTs are not normal types. Due to their lack of a statically known
16
- size, these types can only exist behind some kind of pointer. Any pointer to a
17
- DST consequently becomes a * fat * pointer consisting of the pointer and the
15
+ this regard, DSTs are not normal types. Because they lack a statically known
16
+ size, these types can only exist behind a pointer. Any pointer to a
17
+ DST consequently becomes a * wide * pointer consisting of the pointer and the
18
18
information that "completes" them (more on this below).
19
19
20
- There are two major DSTs exposed by the language: trait objects, and slices.
20
+ There are two major DSTs exposed by the language:
21
+
22
+ * trait objects: ` dyn MyTrait `
23
+ * slices: ` [T] ` , ` str ` , and others
21
24
22
25
A trait object represents some type that implements the traits it specifies.
23
26
The exact original type is * erased* in favor of runtime reflection
24
27
with a vtable containing all the information necessary to use the type.
25
- This is the information that completes a trait object: a pointer to its vtable.
28
+ The information that completes a trait object pointer is the vtable pointer.
29
+ The runtime size of the pointee can be dynamically requested from the vtable.
26
30
27
31
A slice is simply a view into some contiguous storage -- typically an array or
28
- ` Vec ` . The information that completes a slice is just the number of elements
29
- it points to.
32
+ ` Vec ` . The information that completes a slice pointer is just the number of elements
33
+ it points to. The runtime size of the pointee is just the statically known size
34
+ of an element multiplied by the number of elements.
30
35
31
36
Structs can actually store a single DST directly as their last field, but this
32
37
makes them a DST as well:
33
38
34
39
``` rust
35
40
// Can't be stored on the stack directly
36
- struct Foo {
41
+ struct MySuperSlice {
37
42
info : u32 ,
38
43
data : [u8 ],
39
44
}
40
45
```
41
46
47
+ Although such a type is largely useless without a way to construct it. Currently the
48
+ only properly supported way to create a custom DST is by making your type generic
49
+ and performing an * unsizing coercion* :
50
+
51
+ ``` rust
52
+ struct MySuperSliceable <T : ? Sized > {
53
+ info : u32 ,
54
+ data : T
55
+ }
56
+
57
+ fn main () {
58
+ let sized : MySuperSliceable <[u8 ; 8 ]> = MySuperSliceable {
59
+ info : 17 ,
60
+ data : [0 ; 8 ],
61
+ };
62
+
63
+ let dynamic : & MySuperSliceable <[u8 ]> = & sized ;
64
+
65
+ // prints: "17 [0, 0, 0, 0, 0, 0, 0, 0]"
66
+ println! (" {} {:?}" , dynamic . info, & dynamic . data);
67
+ }
68
+ ```
69
+
70
+ (Yes, custom DSTs are a largely half-baked feature for now.)
71
+
72
+
73
+
74
+
42
75
43
76
# Zero Sized Types (ZSTs)
44
77
45
- Rust actually allows types to be specified that occupy no space:
78
+ Rust also allows types to be specified that occupy no space:
46
79
47
80
``` rust
48
- struct Foo ; // No fields = no size
81
+ struct Nothing ; // No fields = no size
49
82
50
83
// All fields have no size = no size
51
- struct Baz {
52
- foo : Foo ,
84
+ struct LotsOfNothing {
85
+ foo : Nothing ,
53
86
qux : (), // empty tuple has no size
54
87
baz : [u8 ; 0 ], // empty array has no size
55
88
}
56
89
```
57
90
58
91
On their own, Zero Sized Types (ZSTs) are, for obvious reasons, pretty useless.
59
92
However as with many curious layout choices in Rust, their potential is realized
60
- in a generic context: Rust largely understands that any operation that produces
61
- or stores a ZST can be reduced to a no-op. First off, storing it doesn't even
62
- make sense -- it doesn't occupy any space. Also there's only one value of that
63
- type, so anything that loads it can just produce it from the aether -- which is
93
+ in a generic context: Rust largely understands that any operation that produces
94
+ or stores a ZST can be reduced to a no-op. First off, storing it doesn't even
95
+ make sense -- it doesn't occupy any space. Also there's only one value of that
96
+ type, so anything that loads it can just produce it from the aether -- which is
64
97
also a no-op since it doesn't occupy any space.
65
98
66
- One of the most extreme example's of this is Sets and Maps. Given a
99
+ One of the most extreme examples of this is Sets and Maps. Given a
67
100
` Map<Key, Value> ` , it is common to implement a ` Set<Key> ` as just a thin wrapper
68
101
around ` Map<Key, UselessJunk> ` . In many languages, this would necessitate
69
102
allocating space for UselessJunk and doing work to store and load UselessJunk
@@ -78,9 +111,8 @@ support values.
78
111
79
112
Safe code need not worry about ZSTs, but * unsafe* code must be careful about the
80
113
consequence of types with no size. In particular, pointer offsets are no-ops,
81
- and standard allocators (including jemalloc, the one used by default in Rust)
82
- may return ` nullptr ` when a zero-sized allocation is requested, which is
83
- indistinguishable from out of memory.
114
+ and standard allocators may return ` null ` when a zero-sized allocation is
115
+ requested, which is indistinguishable from the out of memory result.
84
116
85
117
86
118
@@ -97,7 +129,7 @@ enum Void {} // No variants = EMPTY
97
129
```
98
130
99
131
Empty types are even more marginal than ZSTs. The primary motivating example for
100
- Void types is type-level unreachability. For instance, suppose an API needs to
132
+ an empty type is type-level unreachability. For instance, suppose an API needs to
101
133
return a Result in general, but a specific case actually is infallible. It's
102
134
actually possible to communicate this at the type level by returning a
103
135
` Result<T, Void> ` . Consumers of the API can confidently unwrap such a Result
@@ -125,9 +157,35 @@ But this trick doesn't work yet.
125
157
126
158
One final subtle detail about empty types is that raw pointers to them are
127
159
actually valid to construct, but dereferencing them is Undefined Behavior
128
- because that doesn't actually make sense. That is, you could model C's ` void * `
129
- type with ` *const Void ` , but this doesn't necessarily gain anything over using
130
- e.g. ` *const () ` , which * is* safe to randomly dereference.
160
+ because that wouldn't make sense.
161
+
162
+ We recommend against modelling C's ` void* ` type with ` *const Void ` .
163
+ A lot of people started doing that but quickly ran into trouble because
164
+ Rust doesn't really have any safety guards against trying to instantiate
165
+ empty types with unsafe code, and if you do it, it's Undefined Behaviour.
166
+ This was especially problematic because developers had a habit of converting
167
+ raw pointers to references and ` &Void ` is * also* Undefined Behaviour to
168
+ construct.
169
+
170
+ ` *const () ` (or equivalent) works reasonably well for ` void* ` , and can be made
171
+ into a reference without any safety problems. It still doesn't prevent you from
172
+ trying to read or write values, but at least it compiles to a no-op instead
173
+ of UB.
174
+
175
+
176
+
177
+
178
+
179
+ # Extern Types
180
+
181
+ There is [ an accepted RFC] [ extern-types ] to add proper types with an unknown size,
182
+ called * extern types* , which would let Rust developers model things like C's ` void* `
183
+ and other "declared but never defined" types more accurately. However as of
184
+ Rust 2018, the feature is stuck in limbo over how ` size_of::<MyExternType>() `
185
+ should behave.
186
+
187
+
131
188
132
189
133
190
[ dst-issue ] : https://github.com/rust-lang/rust/issues/26403
191
+ [ extern-types ] : https://github.com/rust-lang/rfcs/blob/master/text/1861-extern-types.md
0 commit comments