Skip to content

Commit 1444990

Browse files
committed
Cleanup and HistoryBuffer is now const and using MaybeUninit
1 parent aee0817 commit 1444990

File tree

5 files changed

+81
-45
lines changed

5 files changed

+81
-45
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
authors = [
33
"Jorge Aparicio <jorge@japaric.io>",
44
"Per Lindgren <per.lindgren@ltu.se>",
5+
"Emil Fresk <emil.fresk@gmail.com>",
56
]
67
categories = [
78
"data-structures",

src/histbuf.rs

Lines changed: 77 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,65 @@
1+
use core::mem::MaybeUninit;
2+
use core::ptr;
3+
use core::slice;
4+
15
/// A "history buffer", similar to a write-only ring buffer of fixed length.
26
///
37
/// This buffer keeps a fixed number of elements. On write, the oldest element
48
/// is overwritten. Thus, the buffer is useful to keep a history of values with
59
/// some desired depth, and for example calculate a rolling average.
610
///
7-
/// The buffer is always fully initialized; depending on the constructor, the
8-
/// initial value is either the default value for the element type or a supplied
9-
/// initial value. This simplifies the API and is mostly irrelevant for the
10-
/// intended use case.
11-
///
1211
/// # Examples
1312
/// ```
1413
/// use heapless::HistoryBuffer;
1514
///
1615
/// // Initialize a new buffer with 8 elements, all initially zero.
1716
/// let mut buf = HistoryBuffer::<_, 8>::new();
1817
///
18+
/// // Starts with no data
19+
/// assert_eq!(buf.recent(), None);
20+
///
1921
/// buf.write(3);
2022
/// buf.write(5);
2123
/// buf.extend(&[4, 4]);
2224
///
2325
/// // The most recent written element is a four.
24-
/// assert_eq!(buf.recent(), &4);
26+
/// assert_eq!(buf.recent(), Some(&4));
2527
///
2628
/// // To access all elements in an unspecified order, use `as_slice()`.
2729
/// for el in buf.as_slice() { println!("{:?}", el); }
2830
///
29-
/// // Now we can prepare an average of all values, which comes out to 2.
31+
/// // Now we can prepare an average of all values, which comes out to 4.
3032
/// let avg = buf.as_slice().iter().sum::<usize>() / buf.len();
31-
/// assert_eq!(avg, 2);
33+
/// assert_eq!(avg, 4);
3234
/// ```
33-
#[derive(Clone)]
3435
pub struct HistoryBuffer<T, const N: usize> {
35-
data: [T; N],
36+
data: [MaybeUninit<T>; N],
3637
write_at: usize,
38+
filled: bool,
3739
}
3840

39-
impl<T, const N: usize> HistoryBuffer<T, N>
40-
where
41-
T: Default + Copy,
42-
{
43-
/// Constructs a new history buffer, where every element is filled with the
44-
/// default value of the type `T`.
41+
impl<T, const N: usize> HistoryBuffer<T, N> {
42+
const INIT: MaybeUninit<T> = MaybeUninit::uninit();
43+
44+
/// Constructs a new history buffer.
4545
///
46-
/// `HistoryBuffer` currently cannot be constructed in `const` context.
46+
/// The construction of a `HistoryBuffer` works in `const` contexts.
4747
///
4848
/// # Examples
4949
///
5050
/// ```
5151
/// use heapless::HistoryBuffer;
5252
///
5353
/// // Allocate a 16-element buffer on the stack
54-
/// let mut x: HistoryBuffer<u8, 16> = HistoryBuffer::new();
55-
/// // All elements are zero
56-
/// assert_eq!(x.as_slice(), [0; 16]);
54+
/// let x: HistoryBuffer<u8, 16> = HistoryBuffer::new();
55+
/// assert_eq!(x.len(), 0);
5756
/// ```
58-
pub fn new() -> Self {
57+
#[inline]
58+
pub const fn new() -> Self {
5959
Self {
60-
// seems not yet implemented
61-
// data: Default::default(),
62-
data: [T::default(); N],
60+
data: [Self::INIT; N],
6361
write_at: 0,
62+
filled: false,
6463
}
6564
}
6665

@@ -87,10 +86,12 @@ where
8786
/// // All elements are four
8887
/// assert_eq!(x.as_slice(), [4; 16]);
8988
/// ```
89+
#[inline]
9090
pub fn new_with(t: T) -> Self {
9191
Self {
92-
data: [t; N],
92+
data: [MaybeUninit::new(t); N],
9393
write_at: 0,
94+
filled: true,
9495
}
9596
}
9697

@@ -101,18 +102,35 @@ where
101102
}
102103

103104
impl<T, const N: usize> HistoryBuffer<T, N> {
105+
/// Returns the current fill level of the buffer.
106+
#[inline]
107+
pub fn len(&self) -> usize {
108+
if self.filled {
109+
N
110+
} else {
111+
self.write_at
112+
}
113+
}
114+
104115
/// Returns the capacity of the buffer, which is the length of the
105116
/// underlying backing array.
106-
pub fn len(&self) -> usize {
107-
self.data.len()
117+
#[inline]
118+
pub fn capacity(&self) -> usize {
119+
N
108120
}
109121

110122
/// Writes an element to the buffer, overwriting the oldest value.
111123
pub fn write(&mut self, t: T) {
112-
self.data[self.write_at] = t;
124+
if self.filled {
125+
// Drop the old before we overwrite it.
126+
unsafe { ptr::drop_in_place(self.data[self.write_at].as_mut_ptr()) }
127+
}
128+
self.data[self.write_at] = MaybeUninit::new(t);
129+
113130
self.write_at += 1;
114-
if self.write_at == self.len() {
131+
if self.write_at == self.capacity() {
115132
self.write_at = 0;
133+
self.filled = true;
116134
}
117135
}
118136

@@ -139,20 +157,28 @@ impl<T, const N: usize> HistoryBuffer<T, N> {
139157
/// let mut x: HistoryBuffer<u8, 16> = HistoryBuffer::new();
140158
/// x.write(4);
141159
/// x.write(10);
142-
/// assert_eq!(x.recent(), &10);
160+
/// assert_eq!(x.recent(), Some(&10));
143161
/// ```
144-
pub fn recent(&self) -> &T {
162+
pub fn recent(&self) -> Option<&T> {
145163
if self.write_at == 0 {
146-
&self.data[self.len() - 1]
164+
if self.filled {
165+
Some(unsafe { &*self.data[self.capacity() - 1].as_ptr() })
166+
} else {
167+
None
168+
}
147169
} else {
148-
&self.data[self.write_at - 1]
170+
Some(unsafe { &*self.data[self.write_at - 1].as_ptr() })
149171
}
150172
}
151173

152174
/// Returns the array slice backing the buffer, without keeping track
153175
/// of the write position. Therefore, the element order is unspecified.
154176
pub fn as_slice(&self) -> &[T] {
155-
&self.data
177+
if self.filled {
178+
unsafe { slice::from_raw_parts(self.data.as_ptr() as *const _, self.capacity()) }
179+
} else {
180+
unsafe { slice::from_raw_parts(self.data.as_ptr() as *const _, self.write_at) }
181+
}
156182
}
157183
}
158184

@@ -179,6 +205,17 @@ where
179205
}
180206
}
181207

208+
impl<T, const N: usize> Drop for HistoryBuffer<T, N> {
209+
fn drop(&mut self) {
210+
unsafe {
211+
ptr::drop_in_place(ptr::slice_from_raw_parts_mut(
212+
self.data.as_mut_ptr() as *mut T,
213+
self.len(),
214+
))
215+
}
216+
}
217+
}
218+
182219
#[cfg(test)]
183220
mod tests {
184221
use crate::HistoryBuffer;
@@ -190,15 +227,15 @@ mod tests {
190227
assert_eq!(x.as_slice(), [1; 4]);
191228

192229
let x: HistoryBuffer<u8, 4> = HistoryBuffer::new();
193-
assert_eq!(x.as_slice(), [0; 4]);
230+
assert_eq!(x.as_slice(), []);
194231
}
195232

196233
#[test]
197234
fn write() {
198235
let mut x: HistoryBuffer<u8, 4> = HistoryBuffer::new();
199236
x.write(1);
200237
x.write(4);
201-
assert_eq!(x.as_slice(), [1, 4, 0, 0]);
238+
assert_eq!(x.as_slice(), [1, 4]);
202239

203240
x.write(5);
204241
x.write(6);
@@ -213,7 +250,7 @@ mod tests {
213250
fn clear() {
214251
let mut x: HistoryBuffer<u8, 4> = HistoryBuffer::new_with(1);
215252
x.clear();
216-
assert_eq!(x.as_slice(), [0; 4]);
253+
assert_eq!(x.as_slice(), []);
217254

218255
let mut x: HistoryBuffer<u8, 4> = HistoryBuffer::new();
219256
x.clear_with(1);
@@ -223,16 +260,16 @@ mod tests {
223260
#[test]
224261
fn recent() {
225262
let mut x: HistoryBuffer<u8, 4> = HistoryBuffer::new();
226-
assert_eq!(x.recent(), &0);
263+
assert_eq!(x.recent(), None);
227264

228265
x.write(1);
229266
x.write(4);
230-
assert_eq!(x.recent(), &4);
267+
assert_eq!(x.recent(), Some(&4));
231268

232269
x.write(5);
233270
x.write(6);
234271
x.write(10);
235-
assert_eq!(x.recent(), &10);
272+
assert_eq!(x.recent(), Some(&10));
236273
}
237274

238275
#[test]

src/indexmap.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,6 @@ where
375375
K: Eq + Hash,
376376
S: Default + Hasher,
377377
{
378-
// TODO turn into a `const fn`; needs `mem::zeroed` to be a `const fn`
379378
/// Creates an empty `IndexMap`.
380379
///
381380
/// **NOTE** This constructor will become a `const fn` in the future

src/lib.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,14 @@
6363
//!
6464
//! # Minimum Supported Rust Version (MSRV)
6565
//!
66-
//! This crate is guaranteed to compile on stable Rust 1.36 and up with its default set of features.
66+
//! This crate is guaranteed to compile on stable Rust 1.51 and up with its default set of features.
6767
//! It *might* compile on older versions but that may change in any new patch release.
6868
69-
// experimental usage of const generics, requires nightly 2020-08-18 (or newer)
7069
#![cfg_attr(not(test), no_std)]
7170
#![deny(missing_docs)]
7271
#![deny(rust_2018_compatibility)]
7372
#![deny(rust_2018_idioms)]
74-
// #![deny(warnings)]
73+
#![deny(warnings)]
7574

7675
pub use binary_heap::BinaryHeap;
7776
pub use histbuf::HistoryBuffer;

src/spsc/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ where
142142

143143
/// A statically allocated single producer single consumer queue with a capacity of `N` elements
144144
///
145-
/// *IMPORTANT*: To get better performance use a capacity that is a power of 2 (e.g. `U16`, `U32`,
145+
/// *IMPORTANT*: To get better performance use a capacity that is a power of 2 (e.g. `16`, `32`,
146146
/// etc.).
147147
///
148148
/// By default `spsc::Queue` will use `usize` integers to hold the indices to its head and tail. For

0 commit comments

Comments
 (0)