brownstone/move_builder.rs
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
/*!
A misuse-immune array builder. See [`ArrayBuilder`] for details and examples.
*/
use crate::builder;
/**
The result of pushing to an [`ArrayBuilder`]. If the push resulted in a
full array, the array is returned directly; otherwise, the builder is
returned with updated state. See [`ArrayBuilder`] for details and examples.
*/
#[derive(Debug, Clone)]
pub enum PushResult<T, const N: usize> {
Full([T; N]),
NotFull(ArrayBuilder<T, N>),
}
/**
Misuse-immune array builder
This `ArrayBuilder` uses move semantics to provide an array builder that
never panics or returns errors. Each call to [`push`][ArrayBuilder::push]
takes `self` by move, and returns either the builder (if it's not full yet)
or the fully initialized array (if it is). The builder therefore can only
exist while the array being built isn't full yet.
```
use brownstone::move_builder::{ArrayBuilder, PushResult};
let builder = match ArrayBuilder::start() {
PushResult::Full(_) => unreachable!(),
PushResult::NotFull(builder) => builder,
};
assert!(builder.is_empty());
let builder = match builder.push(5) {
PushResult::Full(_) => unreachable!(),
PushResult::NotFull(builder) => builder,
};
assert_eq!(builder.len(), 1);
let builder = match builder.push(6) {
PushResult::Full(_) => unreachable!(),
PushResult::NotFull(builder) => builder,
};
assert_eq!(builder.len(), 2);
assert_eq!(builder.finished_slice(), [5, 6]);
let array = match builder.push(7) {
PushResult::Full(array) => array,
PushResult::NotFull(_) => unreachable!(),
};
assert_eq!(array, [5, 6, 7]);
```
*/
#[derive(Debug, Clone)]
pub struct ArrayBuilder<T, const N: usize> {
builder: builder::ArrayBuilder<T, N>,
// Invariant: while this instance exists, the builder is not full
}
impl<T, const N: usize> ArrayBuilder<T, N> {
/**
Create a new [`ArrayBuilder`]. If `N == 0`, immediately return an empty
array, rather than the builder.
*/
#[inline]
pub fn start() -> PushResult<T, N> {
// Invariant preserved: if N == 0, return the array immediately
match builder::ArrayBuilder::new().try_finish() {
Ok(array) => PushResult::Full(array),
Err(builder) => PushResult::NotFull(Self { builder }),
}
}
/**
Returns true if there are no initialized elements in the array.
*/
#[inline]
pub fn is_empty(&self) -> bool {
self.builder.is_empty()
}
/**
Returns the number of initialized elements in the array. Guaranteed to
be less than `N`.
*/
#[inline]
pub fn len(&self) -> usize {
self.builder.len()
}
/**
Add a new initialized element to the array. If this causes the array
to become fully initialized, the array is returned; otherwise, a new
builder is returned.
*/
#[inline]
pub fn push(self, value: T) -> PushResult<T, N> {
// Destructure self to ensure that an ArrayBuilder never exists with
// a full array
let mut builder = self.builder;
// The unsafes here have debug_asserts checking their correctness.
// Invariant presumed: the array is not full, so this push is safe
match unsafe { builder.push_unchecked(value) } {
// Invariant preserved: We only create a new ArrayBuilder if the
// array is not full yet
builder::PushResult::NotFull => PushResult::NotFull(Self { builder }),
builder::PushResult::Full => {
// Invariant preserved: if this push fills the array, the array
// is returned immediately
PushResult::Full(unsafe { builder.finish_unchecked() })
}
}
}
/**
Get the slice of the array that has already been initialized.
*/
#[inline]
pub fn finished_slice(&self) -> &[T] {
self.builder.finished_slice()
}
/**
Get the mutable slice of the array that has already been initialized.
*/
#[inline]
pub fn finished_slice_mut(&mut self) -> &mut [T] {
self.builder.finished_slice_mut()
}
}