]>
Commit | Line | Data |
---|---|---|
c1a9b12d SL |
1 | % PhantomData |
2 | ||
3 | When working with unsafe code, we can often end up in a situation where | |
4 | types or lifetimes are logically associated with a struct, but not actually | |
5 | part of a field. This most commonly occurs with lifetimes. For instance, the | |
6 | `Iter` for `&'a [T]` is (approximately) defined as follows: | |
7 | ||
8 | ```rust,ignore | |
9 | struct Iter<'a, T: 'a> { | |
10 | ptr: *const T, | |
11 | end: *const T, | |
12 | } | |
13 | ``` | |
14 | ||
15 | However because `'a` is unused within the struct's body, it's *unbounded*. | |
16 | Because of the troubles this has historically caused, unbounded lifetimes and | |
17 | types are *forbidden* in struct definitions. Therefore we must somehow refer | |
18 | to these types in the body. Correctly doing this is necessary to have | |
19 | correct variance and drop checking. | |
20 | ||
21 | We do this using `PhantomData`, which is a special marker type. `PhantomData` | |
22 | consumes no space, but simulates a field of the given type for the purpose of | |
23 | static analysis. This was deemed to be less error-prone than explicitly telling | |
24 | the type-system the kind of variance that you want, while also providing other | |
25 | useful such as the information needed by drop check. | |
26 | ||
27 | Iter logically contains a bunch of `&'a T`s, so this is exactly what we tell | |
28 | the PhantomData to simulate: | |
29 | ||
30 | ``` | |
31 | use std::marker; | |
32 | ||
33 | struct Iter<'a, T: 'a> { | |
34 | ptr: *const T, | |
35 | end: *const T, | |
36 | _marker: marker::PhantomData<&'a T>, | |
37 | } | |
38 | ``` | |
39 | ||
40 | and that's it. The lifetime will be bounded, and your iterator will be variant | |
41 | over `'a` and `T`. Everything Just Works. | |
42 | ||
43 | Another important example is Vec, which is (approximately) defined as follows: | |
44 | ||
45 | ``` | |
46 | struct Vec<T> { | |
47 | data: *const T, // *const for variance! | |
48 | len: usize, | |
49 | cap: usize, | |
50 | } | |
51 | ``` | |
52 | ||
5bcae85e SL |
53 | Unlike the previous example, it *appears* that everything is exactly as we |
54 | want. Every generic argument to Vec shows up in at least one field. | |
c1a9b12d SL |
55 | Good to go! |
56 | ||
57 | Nope. | |
58 | ||
5bcae85e | 59 | The drop checker will generously determine that `Vec<T>` does not own any values |
c1a9b12d SL |
60 | of type T. This will in turn make it conclude that it doesn't need to worry |
61 | about Vec dropping any T's in its destructor for determining drop check | |
62 | soundness. This will in turn allow people to create unsoundness using | |
63 | Vec's destructor. | |
64 | ||
65 | In order to tell dropck that we *do* own values of type T, and therefore may | |
66 | drop some T's when *we* drop, we must add an extra PhantomData saying exactly | |
67 | that: | |
68 | ||
69 | ``` | |
70 | use std::marker; | |
71 | ||
72 | struct Vec<T> { | |
73 | data: *const T, // *const for covariance! | |
74 | len: usize, | |
75 | cap: usize, | |
76 | _marker: marker::PhantomData<T>, | |
77 | } | |
78 | ``` | |
79 | ||
80 | Raw pointers that own an allocation is such a pervasive pattern that the | |
81 | standard library made a utility for itself called `Unique<T>` which: | |
82 | ||
83 | * wraps a `*const T` for variance | |
5bcae85e | 84 | * includes a `PhantomData<T>` |
c1a9b12d SL |
85 | * auto-derives Send/Sync as if T was contained |
86 | * marks the pointer as NonZero for the null-pointer optimization |