Skip to content

Commit 221b65e

Browse files
committed
next_array: Revise safety comments, Drop, and push
1 parent 70842fc commit 221b65e

File tree

1 file changed

+99
-17
lines changed

1 file changed

+99
-17
lines changed

src/next_array.rs

+99-17
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ struct ArrayBuilder<T, const N: usize> {
1717
impl<T, const N: usize> ArrayBuilder<T, N> {
1818
/// Initializes a new, empty `ArrayBuilder`.
1919
pub fn new() -> Self {
20-
// SAFETY: the validity invariant trivially hold for a zero-length array.
20+
// SAFETY: The safety invariant of `arr` trivially holds for `len = 0`.
2121
Self {
2222
arr: [(); N].map(|_| MaybeUninit::uninit()),
2323
len: 0,
@@ -28,42 +28,124 @@ impl<T, const N: usize> ArrayBuilder<T, N> {
2828
///
2929
/// # Panics
3030
///
31-
/// This panics if `self.len() >= N`.
31+
/// This panics if `self.len >= N` or if `self.len == usize::MAX`.
3232
pub fn push(&mut self, value: T) {
33-
// SAFETY: we maintain the invariant here that arr[..len] is valid.
34-
// Indexing with self.len also ensures self.len < N, and thus <= N after
35-
// the increment.
33+
// PANICS: This will panic if `self.len >= N`.
34+
// SAFETY: The safety invariant of `self.arr` applies to elements at
35+
// indices `0..self.len` — not to the element at `self.len`. Writing to
36+
// the element at index `self.len` therefore does not violate the safety
37+
// invariant of `self.arr`. Even if this line panics, we have not
38+
// created any intermediate invalid state.
3639
self.arr[self.len] = MaybeUninit::new(value);
37-
self.len += 1;
40+
// PANICS: This will panic if `self.len == usize::MAX`.
41+
// SAFETY: By invariant on `self.arr`, all elements at indicies
42+
// `0..self.len` are valid. Due to the above write, the element at
43+
// `self.len` is now also valid. Consequently, all elements at indicies
44+
// `0..(self.len + 1)` are valid, and `self.len` can be safely
45+
// incremented without violating `self.arr`'s invariant. It is fine if
46+
// this increment panics, as we have not created any intermediate
47+
// invalid state.
48+
self.len = match self.len.checked_add(1) {
49+
Some(sum) => sum,
50+
None => panic!("`self.len == usize::MAX`; cannot increment `len`"),
51+
};
3852
}
3953

40-
/// Consumes the elements in the `ArrayBuilder` and returns them as an array `[T; N]`.
54+
/// Consumes the elements in the `ArrayBuilder` and returns them as an array
55+
/// `[T; N]`.
4156
///
4257
/// If `self.len() < N`, this returns `None`.
4358
pub fn take(&mut self) -> Option<[T; N]> {
4459
if self.len == N {
45-
// Take the array, resetting our length back to zero.
60+
// SAFETY: Decreasing the value of `self.len` cannot violate the
61+
// safety invariant on `self.arr`.
4662
self.len = 0;
63+
64+
// SAFETY: Since `self.len` is 0, `self.arr` may safely contain
65+
// uninitialized elements.
4766
let arr = mem::replace(&mut self.arr, [(); N].map(|_| MaybeUninit::uninit()));
4867

49-
// SAFETY: we had len == N, so all elements in arr are valid.
50-
Some(unsafe { arr.map(|v| v.assume_init()) })
68+
Some(arr.map(|v| {
69+
// SAFETY: We know that all elements of `arr` are valid because
70+
// we checked that `len == N`.
71+
unsafe { v.assume_init() }
72+
}))
5173
} else {
5274
None
5375
}
5476
}
5577
}
5678

5779
impl<T, const N: usize> Drop for ArrayBuilder<T, N> {
80+
// We provide a non-trivial `Drop` impl, because the trivial impl would be a
81+
// no-op; `MaybeUninit<T>` has no innate awareness of its own validity, and
82+
// so it can only forget its contents. By leveraging the safety invariant of
83+
// `self.arr`, we do know which elements of `self.arr` are valid, and can
84+
// selectively run their destructors.
5885
fn drop(&mut self) {
86+
// Select the valid elements of `self.arr`.
87+
//
88+
// LEMMA 1: The elements of `valid` reference the valid elements of
89+
// `self.arr`.
90+
//
91+
// PROOF: `slice::split_at_mut(mid)` produces a pair of slices, the
92+
// first of which contains the elements at the indices `0..mid`. By
93+
// invariant on `self.arr`, the elements of `self.arr` at indicies
94+
// `0..self.len` are valid. Assuming that `slice::split_at_mut` is
95+
// correctly implemented, the slice `valid` will only reference the
96+
// valid elements of `self.arr`.
97+
let (valid, _) = self.arr.split_at_mut(self.len);
98+
99+
// Cast `valid` from `&[MaybeUninit<T>]` to `&[T]`
100+
//
101+
// `align_to_mut` guarantees that the length of the casted slice will be
102+
// as long as possible within the constraints of the source and
103+
// destination element types' alignments and sizes. Since
104+
// `MaybeUninit<T>` and `T` have identical alignments and sizes [1], all
105+
// elements will be casted and the prefix and suffix components of the
106+
// return value will be empty and `valid` will contain all of the
107+
// elements that it did prior to the cast.
108+
//
109+
// SAFETY: It is sound to cast a `MaybeUninit<T>` that contains a valid
110+
// `T` to a `T`. A `MaybeUninit<T>` is guaranteed to have the same size,
111+
// alignment, and ABI as `T` [1], and by LEMMA 1, `valid` consists only
112+
// of `MaybeUninit<T>` in the initialized state.
113+
//
114+
// [1]: https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#layout-1
115+
let (_, valid, _): (_, &mut [T], _) = unsafe { valid.align_to_mut::<T>() };
116+
117+
// LEMMA 2: `valid_ptr` has exactly the same safety invariants as
118+
// `valid`.
119+
//
120+
// PROOF: We assume that `slice::as_mut_ptr` correctly produces a raw
121+
// `mut` slice pointing to the same elements as its receiver. Such a
122+
// pointer will be valid for both reads and writes, be properly aligned,
123+
// and be non-null. By `mem::forget`ting `valid`, we additionally ensure
124+
// that `valid_ptr` is the *only* pointer to its referent.
125+
let valid_ptr = {
126+
let ptr = valid.as_mut_ptr();
127+
// Move `valid` out of the surrounding scope and immediately drop
128+
// it. `ptr` is now the only pointer to `valid`'s referent.
129+
drop(valid);
130+
ptr
131+
};
132+
133+
// Run the destructors of `valid_ptr`'s elements.
134+
//
135+
// SAFETY:
136+
// - `valid_ptr`, by LEMMA 2, is valid for both reads and writes
137+
// - `valid_ptr`, by LEMMA 2, is properly aligned
138+
// - `valid_ptr`, by LEMMA 2, is non-null
139+
// - `valid_ptr`, by LEMMA 2, is valid for dropping, because it is data
140+
// owned by the `ArrayBuilder` and we place no additional drop-related
141+
// invariants on it
142+
// - `valid_ptr`, by LEMMA 2, is the only pointer to its referent, and
143+
// therefore its referent cannot be concurrently accessed during the
144+
// execution of `ptr::drop_in_place`.
145+
// - The referent of `valid_ptr`, which may not be `Copy` is not re-used
146+
// after the invocation of `ptr::drop_in_place`.
59147
unsafe {
60-
// SAFETY: arr[..len] is valid, so must be dropped. First we create
61-
// a pointer to this valid slice, then drop that slice in-place.
62-
// The cast from *mut MaybeUninit<T> to *mut T is always sound by
63-
// the layout guarantees of MaybeUninit.
64-
let ptr_to_first: *mut MaybeUninit<T> = self.arr.as_mut_ptr();
65-
let ptr_to_slice = ptr::slice_from_raw_parts_mut(ptr_to_first.cast::<T>(), self.len);
66-
ptr::drop_in_place(ptr_to_slice);
148+
ptr::drop_in_place(valid_ptr);
67149
}
68150
}
69151
}

0 commit comments

Comments
 (0)