@@ -9,19 +9,24 @@ is not always the case, however.
9
9
10
10
# Dynamically Sized Types (DSTs)
11
11
12
- Rust also supports types without a statically known size. On the surface, this
13
- is a bit nonsensical: Rust * must* know the size of something in order to work
14
- with it! DSTs are generally produced as views, or through type-erasure of types
15
- that * do* have a known size. Due to their lack of a statically known size, these
16
- types can only exist * behind* some kind of pointer. They consequently produce a
17
- * fat* pointer consisting of the pointer and the information that * completes*
18
- them.
19
-
20
- For instance, the slice type, ` [T] ` , is some statically unknown number of
21
- elements stored contiguously. ` &[T] ` consequently consists of a ` (&T, usize) `
22
- pair that specifies where the slice starts, and how many elements it contains.
23
- Similarly, Trait Objects support interface-oriented type erasure through a
24
- ` (data_ptr, vtable_ptr) ` pair.
12
+ Rust in fact supports Dynamically Sized Types (DSTs): types without a statically
13
+ known size or alignment. On the surface, this is a bit nonsensical: Rust * must*
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
18
+ information that "completes" them (more on this below).
19
+
20
+ There are two major DSTs exposed by the language: trait objects, and slices.
21
+
22
+ A trait object represents some type that implements the traits it specifies.
23
+ The exact original type is * erased* in favour of runtime reflection
24
+ 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.
26
+
27
+ 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.
25
30
26
31
Structs can actually store a single DST directly as their last field, but this
27
32
makes them a DST as well:
@@ -34,8 +39,8 @@ struct Foo {
34
39
}
35
40
```
36
41
37
- ** NOTE: As of Rust 1.0 struct DSTs are broken if the last field has
38
- a variable position based on its alignment.**
42
+ ** NOTE: [ As of Rust 1.0 struct DSTs are broken if the last field has
43
+ a variable position based on its alignment] [ dst-issue ] .**
39
44
40
45
41
46
@@ -56,22 +61,32 @@ struct Baz {
56
61
}
57
62
```
58
63
59
- On their own, ZSTs are, for obvious reasons, pretty useless. However as with
60
- many curious layout choices in Rust, their potential is realized in a generic
61
- context.
62
-
63
- Rust largely understands that any operation that produces or stores a ZST can be
64
- reduced to a no-op. For instance, a ` HashSet<T> ` can be effeciently implemented
65
- as a thin wrapper around ` HashMap<T, ()> ` because all the operations ` HashMap `
66
- normally does to store and retrieve values will be completely stripped in
67
- monomorphization.
68
-
69
- Similarly ` Result<(), ()> ` and ` Option<()> ` are effectively just fancy ` bool ` s.
64
+ On their own, Zero Sized Types (ZSTs) are, for obvious reasons, pretty useless.
65
+ However as with many curious layout choices in Rust, their potential is realized
66
+ in a generic context: Rust largely understands that any operation that produces
67
+ or stores a ZST can be reduced to a no-op. First off, storing it doesn't even
68
+ make sense -- it doesn't occupy any space. Also there's only one value of that
69
+ type, so anything that loads it can just produce it from the aether -- which is
70
+ also a no-op since it doesn't occupy any space.
71
+
72
+ One of the most extreme example's of this is Sets and Maps. Given a
73
+ ` Map<Key, Value> ` , it is common to implement a ` Set<Key> ` as just a thin wrapper
74
+ around ` Map<Key, UselessJunk> ` . In many languages, this would necessitate
75
+ allocating space for UselessJunk and doing work to store and load UselessJunk
76
+ only to discard it. Proving this unnecessary would be a difficult analysis for
77
+ the compiler.
78
+
79
+ However in Rust, we can just say that ` Set<Key> = Map<Key, ()> ` . Now Rust
80
+ statically knows that every load and store is useless, and no allocation has any
81
+ size. The result is that the monomorphized code is basically a custom
82
+ implementation of a HashSet with none of the overhead that HashMap would have to
83
+ support values.
70
84
71
85
Safe code need not worry about ZSTs, but * unsafe* code must be careful about the
72
86
consequence of types with no size. In particular, pointer offsets are no-ops,
73
- and standard allocators (including jemalloc, the one used by Rust) generally
74
- consider passing in ` 0 ` as Undefined Behaviour.
87
+ and standard allocators (including jemalloc, the one used by default in Rust)
88
+ generally consider passing in ` 0 ` for the size of an allocation as Undefined
89
+ Behaviour.
75
90
76
91
77
92
@@ -93,11 +108,12 @@ return a Result in general, but a specific case actually is infallible. It's
93
108
actually possible to communicate this at the type level by returning a
94
109
` Result<T, Void> ` . Consumers of the API can confidently unwrap such a Result
95
110
knowing that it's * statically impossible* for this value to be an ` Err ` , as
96
- this would require providing a value of type Void.
111
+ this would require providing a value of type ` Void ` .
97
112
98
113
In principle, Rust can do some interesting analyses and optimizations based
99
114
on this fact. For instance, ` Result<T, Void> ` could be represented as just ` T ` ,
100
- because the Err case doesn't actually exist. The following * could* also compile:
115
+ because the ` Err ` case doesn't actually exist. The following * could* also
116
+ compile:
101
117
102
118
``` rust,ignore
103
119
enum Void {}
@@ -116,3 +132,6 @@ actually valid to construct, but dereferencing them is Undefined Behaviour
116
132
because that doesn't actually make sense. That is, you could model C's ` void * `
117
133
type with ` *const Void ` , but this doesn't necessarily gain anything over using
118
134
e.g. ` *const () ` , which * is* safe to randomly dereference.
135
+
136
+
137
+ [ dst-issue ] : https://github.com/rust-lang/rust/issues/26403
0 commit comments