]>
Commit | Line | Data |
---|---|---|
8bb4bdeb | 1 | # Working with Unsafe |
c1a9b12d SL |
2 | |
3 | Rust generally only gives us the tools to talk about Unsafe Rust in a scoped and | |
4 | binary manner. Unfortunately, reality is significantly more complicated than | |
5 | that. For instance, consider the following toy function: | |
6 | ||
7 | ```rust | |
8 | fn index(idx: usize, arr: &[u8]) -> Option<u8> { | |
9 | if idx < arr.len() { | |
10 | unsafe { | |
11 | Some(*arr.get_unchecked(idx)) | |
12 | } | |
13 | } else { | |
14 | None | |
15 | } | |
16 | } | |
17 | ``` | |
18 | ||
3b2f2976 | 19 | This function is safe and correct. We check that the index is in bounds, and if it |
c1a9b12d SL |
20 | is, index into the array in an unchecked manner. But even in such a trivial |
21 | function, the scope of the unsafe block is questionable. Consider changing the | |
22 | `<` to a `<=`: | |
23 | ||
24 | ```rust | |
25 | fn index(idx: usize, arr: &[u8]) -> Option<u8> { | |
26 | if idx <= arr.len() { | |
27 | unsafe { | |
28 | Some(*arr.get_unchecked(idx)) | |
29 | } | |
30 | } else { | |
31 | None | |
32 | } | |
33 | } | |
34 | ``` | |
35 | ||
36 | This program is now unsound, and yet *we only modified safe code*. This is the | |
37 | fundamental problem of safety: it's non-local. The soundness of our unsafe | |
38 | operations necessarily depends on the state established by otherwise | |
39 | "safe" operations. | |
40 | ||
41 | Safety is modular in the sense that opting into unsafety doesn't require you | |
42 | to consider arbitrary other kinds of badness. For instance, doing an unchecked | |
43 | index into a slice doesn't mean you suddenly need to worry about the slice being | |
44 | null or containing uninitialized memory. Nothing fundamentally changes. However | |
45 | safety *isn't* modular in the sense that programs are inherently stateful and | |
46 | your unsafe operations may depend on arbitrary other state. | |
47 | ||
3b2f2976 XL |
48 | This non-locality gets much worse when we incorporate actual persistent state. |
49 | Consider a simple implementation of `Vec`: | |
c1a9b12d SL |
50 | |
51 | ```rust | |
52 | use std::ptr; | |
53 | ||
3b2f2976 | 54 | // Note: This definition is naive. See the chapter on implementing Vec. |
c1a9b12d SL |
55 | pub struct Vec<T> { |
56 | ptr: *mut T, | |
57 | len: usize, | |
58 | cap: usize, | |
59 | } | |
60 | ||
61 | // Note this implementation does not correctly handle zero-sized types. | |
3b2f2976 | 62 | // See the chapter on implementing Vec. |
c1a9b12d SL |
63 | impl<T> Vec<T> { |
64 | pub fn push(&mut self, elem: T) { | |
65 | if self.len == self.cap { | |
66 | // not important for this example | |
67 | self.reallocate(); | |
68 | } | |
69 | unsafe { | |
70 | ptr::write(self.ptr.offset(self.len as isize), elem); | |
71 | self.len += 1; | |
72 | } | |
73 | } | |
c1a9b12d SL |
74 | # fn reallocate(&mut self) { } |
75 | } | |
76 | ||
77 | # fn main() {} | |
78 | ``` | |
79 | ||
3b2f2976 | 80 | This code is simple enough to reasonably audit and informally verify. Now consider |
c1a9b12d SL |
81 | adding the following method: |
82 | ||
83 | ```rust,ignore | |
84 | fn make_room(&mut self) { | |
85 | // grow the capacity | |
86 | self.cap += 1; | |
87 | } | |
88 | ``` | |
89 | ||
90 | This code is 100% Safe Rust but it is also completely unsound. Changing the | |
91 | capacity violates the invariants of Vec (that `cap` reflects the allocated space | |
92 | in the Vec). This is not something the rest of Vec can guard against. It *has* | |
93 | to trust the capacity field because there's no way to verify it. | |
94 | ||
ff7c6d11 XL |
95 | Because it relies on invariants of a struct field, this `unsafe` code |
96 | does more than pollute a whole function: it pollutes a whole *module*. | |
c1a9b12d SL |
97 | Generally, the only bullet-proof way to limit the scope of unsafe code is at the |
98 | module boundary with privacy. | |
99 | ||
100 | However this works *perfectly*. The existence of `make_room` is *not* a | |
101 | problem for the soundness of Vec because we didn't mark it as public. Only the | |
102 | module that defines this function can call it. Also, `make_room` directly | |
103 | accesses the private fields of Vec, so it can only be written in the same module | |
104 | as Vec. | |
105 | ||
106 | It is therefore possible for us to write a completely safe abstraction that | |
107 | relies on complex invariants. This is *critical* to the relationship between | |
3b2f2976 XL |
108 | Safe Rust and Unsafe Rust. |
109 | ||
110 | We have already seen that Unsafe code must trust *some* Safe code, but shouldn't | |
111 | trust *generic* Safe code. Privacy is important to unsafe code for similar reasons: | |
112 | it prevents us from having to trust all the safe code in the universe from messing | |
113 | with our trusted state. | |
c1a9b12d SL |
114 | |
115 | Safety lives! | |
116 |