1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
//! The `FixedCapacityVec` data type
//!
//! **STABILITY WARNING**
//!
//! This is a preview of this crate.
//! We intend to tidy it up and make a proper release.

use std::alloc::{self, Layout};
use std::{mem, ptr};

/// Like `Vec` with a capacity fixed at compile time
///
/// When full, can be converted without copying into `Box<[T; N]>`, using `TryFrom`.
///
/// ### Comparison with related data types
///
/// All of the following types store only the actual buffer on the heap,
/// and they are interconvertible without copying the data.
//
// TODO ^ not actually quite true; we should impl Into<Vec< >>
// TODO ^ not actually quite true; we should impl TryFrom<Vec< >>
///
/// | Type          | Size and representation (as eg on stack)  | Full? | Mutability           |
/// |---------------|-----------------------------------------|----------|---------------|
/// | `Vec`         | 3 words: pointer, length, capacity | maybe | indefinitely appendable |
/// | `Box<[T]>`    | 2 words: pointer, length = capacity | always | length fixed at runtime |
/// | `FixedCapacityVec<[T; N]>` | 2 words: pointer, length | maybe | appendable, but capacity fixed at compile time |
/// | `Box<[T; N]>` | 1 word: pointer                    | always | length fixed at compile time |
//
// TODO we should impl Default
// TODO we should impl Deref and DerefMut to [T]
// TODO there should be from_raw_parts and into_raw_parts
// TODO we should impl Clone, Debug, Hash, Eq, Serialize, ...
pub struct FixedCapacityVec<T, const N: usize> {
    /// Data
    ///
    /// ### SAFETY
    ///
    /// Every element of data in 0..len must always be initialised.
    ///
    /// Always a valid, properly aligned, heap pointer to a `[T; N]`;
    /// except, during deconstruction it may be null.
    /// (Deconstruction means methods that consume the `FixedCapacityVec`;
    /// these must typically hand ownership of the allocation to someone else,
    /// but our `Drop::drop` impl will of course still run after that.)
    data: *mut T,

    /// Initialised portion
    ///
    /// **SAFETY**: See `data`
    len: usize,
}

/// Implement `$trait` if `T: $trait`
macro_rules! impl_traits_if_T { { $( $trait:path $(, $unsafe:tt )?; )* } => { $(
    $( $unsafe )? impl<T: $trait, const N: usize> $trait for FixedCapacityVec<T, N> {}
)* } }

// (Vec implements all of these with the same bounds as we do)
impl_traits_if_T! {
    Send, unsafe;
    Sync, unsafe;
    std::panic::UnwindSafe;
    std::panic::RefUnwindSafe;
}

impl<T, const N: usize> FixedCapacityVec<T, N> {
    /// Create a new empty `FixedCapacityVec`, capable of holding up to `N` values of type `T`
    #[inline]
    pub fn new() -> Self {
        // We really want Box::new_uninit() but that's unstable
        let data = unsafe {
            // SAFETY: the Layout is good since we got it from Layout::new
            let data: *mut u8 = alloc::alloc(Self::layout());
            let data: *mut T = data as _;
            data
        };

        FixedCapacityVec { data, len: 0 }
    }

    /// Return the `Layout` for our `data` pointer allocation
    fn layout() -> Layout {
        Layout::new::<[T; N]>()
    }

    /// Return the number of values stored so far
    #[inline]
    pub fn len(&self) -> usize {
        self.len
    }

    /// Returns `true` iff the `FixedCapacityVec` is empty
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.len == 0
    }

    /// Returns `true` iff the `FixedCapacityVec` is full - ie, it has `N` elements
    #[inline]
    pub fn is_full(&self) -> bool {
        self.len == N
    }

    /// Try to append an element
    ///
    /// If the `FixedCapacityVec` is full, ie if it already contains `N` elements,
    /// returns `Err`.
    #[inline]
    // TODO there should be a panic-free try_push public API that returns the item,
    // But that has to be separate from this, because that complicates the optimisation
    fn push_inner(&mut self, item: T) -> Result<(), ()> {
        if self.len >= N {
            return Err(());
        }
        unsafe {
            // SAFETY now len is within bounds and the pointer is aligned
            self.data.add(self.len).write(item);
            // SAFETY now that the value is written, we can say it's there
            self.len += 1;
        }
        Ok(())
    }

    /// Append an element
    ///
    /// # Panics
    ///
    /// Panics if the `FixedCapacityVec` is full, ie if it already contains `N` elements
    #[inline]
    // TODO there should be a panic-free try_push
    pub fn push(&mut self, item: T) {
        self.push_inner(item)
            .expect("pushing to a full FixedCapacityVec");
    }

    // TODO there should be pop and try_pop
}

impl<T, const N: usize> Drop for FixedCapacityVec<T, N> {
    #[inline]
    fn drop(&mut self) {
        if !self.data.is_null() {
            unsafe {
                // SAFETY
                //
                // We are maybe in a deconstructor, but we have checked len and data,
                // so data is valid and aligned and elements up to len are initialised.
                //
                // We are about to break the invariants!  This is OK, because it cannot
                // be observed by anyone: we have &mut Self, so no-one else can see it,
                // and even if a panic unwinds from here, `self` will no longer be considered
                // valid by the language.
                if mem::needs_drop::<T>() {
                    let data: *mut [T] = ptr::slice_from_raw_parts_mut(self.data, self.len);
                    // This causes the supposedly-valid portion of data to become totally
                    // invalid, breaking the invariants.  See above.
                    ptr::drop_in_place(data);
                }
                // SAFETY: this causes self.data to become totally invalid, breaking
                // the invariants.  That's OK; see above.
                alloc::dealloc(self.data as _, Self::layout());
            }
        }
    }
}

/// Convert a full `FixedCapacityVec` into a boxed array.
///
/// If the `FixedCapacityVec` isn't full, it is returned as the `Err`
impl<T, const N: usize> TryFrom<FixedCapacityVec<T, N>> for Box<[T; N]> {
    type Error = FixedCapacityVec<T, N>;

    #[inline]
    fn try_from(mut fcvec: FixedCapacityVec<T, N>) -> Result<Box<[T; N]>, FixedCapacityVec<T, N>> {
        if fcvec.len == N {
            Ok(unsafe {
                // SAFETY
                // We are about to make ptr invalid so we must zero len
                fcvec.len = 0;
                let data: *mut T = mem::replace(&mut fcvec.data, ptr::null_mut());
                // It always was such a valid pointer
                let data: *mut [T; N] = data as _;
                // We have checked that every element is initialised
                // The pointer isn't null since *we* are the deconstructor
                let data: Box<[T; N]> = Box::from_raw(data);
                data
            })
        } else {
            Err(fcvec)
        }
    }
}

#[cfg(test)]
mod test {
    // @@ begin test lint list maintained by maint/add_warning @@
    #![allow(clippy::bool_assert_comparison)]
    #![allow(clippy::clone_on_copy)]
    #![allow(clippy::dbg_macro)]
    #![allow(clippy::print_stderr)]
    #![allow(clippy::print_stdout)]
    #![allow(clippy::single_char_pattern)]
    #![allow(clippy::unwrap_used)]
    #![allow(clippy::unchecked_duration_subtraction)]
    #![allow(clippy::useless_vec)]
    #![allow(clippy::needless_pass_by_value)]
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
    use super::*;
    use std::fmt::Debug;

    const N: usize = 10;
    const H: usize = 5;

    type Fv<T> = FixedCapacityVec<T, N>;

    fn test_api<T: Debug>(mut f: impl FnMut() -> T) {
        println!("make empty");
        let mut v = Fv::<T>::new();
        assert_eq!(v.len(), 0);
        assert!(v.is_empty());
        assert!(!v.is_full());

        println!("fill 0..{H}");
        for i in 0..H {
            v.push(f());
            assert_eq!(v.len(), i + 1);
        }
        assert!(!v.is_empty());
        assert!(!v.is_full());

        println!("fail conversion to boxed array");
        let v: Fv<T> = Box::<[T; N]>::try_from(v).unwrap_err();
        assert_eq!(v.len(), H);

        println!("drop 0..{H}");
        drop(v);

        println!("make empty (2)");
        let mut v = Fv::<T>::new();

        println!("fill 0..{N}");
        for _i in 0..N {
            v.push(f());
        }
        let () = v.push_inner(f()).unwrap_err();

        assert_eq!(v.len(), N);
        assert!(v.is_full());
        assert!(!v.is_empty());

        println!("conversion to boxed array");
        let v: Box<[T; N]> = v.try_into().map_err(|_| ()).expect("not into boxed array");

        println!("drop boxed array");
        drop(v);
    }

    #[test]
    fn api_i32() {
        test_api(|| 42_i32);
    }

    use std::cell::RefCell;
    use std::rc::Rc;

    #[derive(Debug)]
    struct HasDrop {
        index: usize,
        counted: Rc<RefCell<usize>>,
    }
    impl Drop for HasDrop {
        fn drop(&mut self) {
            println!("dropping {}", self.index);
            *self.counted.borrow_mut() -= 1;
        }
    }

    #[test]
    fn api_has_drop() {
        let counted = Rc::new(RefCell::new(0));
        let mut next_index = 0;

        test_api(|| {
            let index = {
                let mut counted = counted.borrow_mut();
                *counted += 1;
                next_index += 1;
                next_index
            };
            println!("creating {}", index);
            HasDrop {
                index,
                counted: counted.clone(),
            }
        });

        assert_eq!(*counted.borrow(), 0);
    }
}