Skip to main content

planck_noalloc/
ringbuf.rs

1//! Circular/ring buffer implementation with fixed capacity.
2//!
3//! This module provides [`RingBuf`], a circular buffer (also known as a ring buffer)
4//! that operates in a First-In-First-Out (FIFO) manner. It's particularly useful for
5//! producer-consumer scenarios, buffering streams of data, or implementing queues
6//! without heap allocation.
7//!
8//! # Overview
9//!
10//! A ring buffer is a fixed-size buffer that wraps around when it reaches the end,
11//! forming a conceptual circle. Elements are added at the head and removed from the
12//! tail, making it ideal for streaming data and FIFO queues.
13//!
14//! # Capacity
15//!
16//! The buffer has a compile-time fixed size `SIZE`, but the actual usable capacity
17//! is `SIZE - 1`. This is a common implementation technique that simplifies the
18//! empty/full detection logic.
19//!
20//! # Use Cases
21//!
22//! Ring buffers are excellent for:
23//! - Buffering serial/UART data in embedded systems
24//! - Implementing producer-consumer queues
25//! - Audio/video frame buffers
26//! - Network packet buffers
27//! - Log message buffers
28//! - Any scenario requiring a fixed-size FIFO queue
29//!
30//! # Performance
31//!
32//! - Push: O(1)
33//! - Pop: O(1)
34//! - Space efficient: uses exactly `SIZE * sizeof(T)` bytes
35//! - Lock-free for single producer/single consumer scenarios
36//!
37//! # Examples
38//!
39//! ```
40//! use planck_noalloc::ringbuf::RingBuf;
41//!
42//! // Create a ring buffer with size 8 (capacity 7)
43//! let mut buf = RingBuf::<u8, 8>::new();
44//!
45//! // Push some data
46//! buf.push(1);
47//! buf.push(2);
48//! buf.push(3);
49//!
50//! // Pop in FIFO order
51//! assert_eq!(buf.pop(), Some(1));
52//! assert_eq!(buf.pop(), Some(2));
53//! assert_eq!(buf.len(), 1);
54//!
55//! // Can push more after popping
56//! buf.push(4);
57//! buf.push(5);
58//!
59//! assert_eq!(buf.len(), 3);
60//! ```
61//!
62//! ## Error Handling
63//!
64//! ```
65//! use planck_noalloc::ringbuf::RingBuf;
66//!
67//! let mut buf = RingBuf::<u8, 4>::new();
68//!
69//! // Fill to capacity (3 elements for size 4)
70//! buf.push(1);
71//! buf.push(2);
72//! buf.push(3);
73//!
74//! // Trying to push beyond capacity returns an error
75//! assert!(buf.try_push(4).is_err());
76//! assert!(buf.is_full());
77//!
78//! // After popping, we can push again
79//! buf.pop();
80//! assert!(buf.try_push(4).is_ok());
81//! ```
82
83use core::mem::MaybeUninit;
84
85/// A Circular / Ring buffer.
86///
87/// A fixed-capacity FIFO (First-In-First-Out) data structure that wraps around
88/// when it reaches the end of its backing array. Elements are added at the head
89/// and removed from the tail.
90///
91/// # Capacity
92///
93/// The buffer is considered 'full' when it contains `SIZE - 1` elements.
94/// This design choice simplifies the empty/full detection logic.
95///
96/// # Type Parameters
97///
98/// - `T`: The type of elements stored (must be `Copy`)
99/// - `SIZE`: The size of the backing array (usable capacity is `SIZE - 1`)
100///
101/// # Examples
102///
103/// ```
104/// use planck_noalloc::ringbuf::RingBuf;
105///
106/// let mut buf = RingBuf::<i32, 16>::new();
107///
108/// buf.push(10);
109/// buf.push(20);
110/// buf.push(30);
111///
112/// assert_eq!(buf.pop(), Some(10));
113/// assert_eq!(buf.pop(), Some(20));
114/// assert_eq!(buf.len(), 1);
115/// ```
116#[derive(Clone, Copy)]
117pub struct RingBuf<T: Copy, const SIZE: usize> {
118    buf: [MaybeUninit<T>; SIZE],
119    head: usize,
120    tail: usize,
121}
122
123impl<T, const N: usize> Default for RingBuf<T, N>
124where
125    T: Copy,
126{
127    fn default() -> Self {
128        Self::new()
129    }
130}
131
132impl<T, const N: usize> RingBuf<T, N>
133where
134    T: core::marker::Copy,
135{
136    /// The total size of the backing array. The usable capacity is `SIZE - 1`.
137    pub const SIZE: usize = N;
138
139    /// Create a new ringbuf with no data inside
140    ///
141    /// This method does not allocate memory.
142    ///
143    /// # Example
144    /// ```
145    /// use planck_noalloc::ringbuf::RingBuf;
146    ///
147    /// // Create an empty ringbuf
148    /// let ringbuf = RingBuf::<u8, 8>::new();
149    /// assert!(ringbuf.is_empty());
150    /// ```
151    #[must_use]
152    pub const fn new() -> Self {
153        Self {
154            buf: [const { MaybeUninit::uninit() }; N],
155            head: 0,
156            tail: 0,
157        }
158    }
159
160    /// Returns true if the ring buffer is empty
161    ///
162    /// # Example
163    /// ```
164    /// use planck_noalloc::ringbuf::RingBuf;
165    ///
166    /// // Create an empty ringbuf
167    /// let mut ringbuf = RingBuf::<u8, 8>::new();
168    /// assert!(ringbuf.is_empty());
169    /// ringbuf.push(42);
170    /// assert!(!ringbuf.is_empty());
171    /// ````
172    #[must_use]
173    pub const fn is_empty(&self) -> bool {
174        // Because we only supported SIZE-1 as maximum capacity,
175        // buf is empty when head == tail
176        self.head == self.tail
177    }
178
179    /// Returns the length of the used buffer
180    ///
181    /// # Example
182    /// ```
183    /// use planck_noalloc::ringbuf::RingBuf;
184    ///
185    /// // Create an empty ringbuf
186    /// let mut ringbuf = RingBuf::<u8, 8>::new();
187    /// assert_eq!(ringbuf.len(), 0);
188    /// ringbuf.push(1);
189    /// ringbuf.push(2);
190    /// ringbuf.push(3);
191    /// assert_eq!(ringbuf.len(), 3);
192    /// # let popped =
193    /// ringbuf.pop();
194    /// # assert_eq!(popped, Some(1));
195    /// assert_eq!(ringbuf.len(), 2);
196    /// ```
197    #[must_use]
198    pub const fn len(&self) -> usize {
199        (self.head + Self::SIZE - self.tail) % Self::SIZE
200    }
201
202    /// Returns true if the buffer is full
203    ///
204    /// # Example
205    /// ```
206    /// use planck_noalloc::ringbuf::RingBuf;
207    ///
208    /// // Create an empty ringbuf
209    /// let mut ringbuf = RingBuf::<u8, 8>::new();
210    /// for i in 0..6 {
211    ///     ringbuf.push(i);
212    /// }
213    /// assert!(!ringbuf.is_full());
214    /// ringbuf.push(6);
215    /// assert!(ringbuf.is_full());
216    /// ````
217    #[must_use]
218    pub const fn is_full(&self) -> bool {
219        (self.head + 1) % N == self.tail
220    }
221
222    /// Returns the maximum number of elements that can be stored in the ringbuf
223    /// This is calculated as the size subtracted 1
224    #[must_use]
225    pub const fn max_capacity(&self) -> usize {
226        Self::SIZE - 1
227    }
228
229    /// Pushes (or enqueues) an element on the ring buffer
230    ///
231    /// # Errors
232    /// Returns an error with the pushed value if the ringbuf is full
233    pub fn try_push(&mut self, x: T) -> Result<(), T> {
234        if self.is_full() {
235            return Err(x);
236        }
237
238        // SAFETY: We checked that it isn't full
239        unsafe { self.push_unchecked(x) };
240        Ok(())
241    }
242
243    /// Pushes (or enqueues) an element on the ring buffer
244    ///
245    /// # Panics
246    /// Panics if the ringbuf is full. If this is not what you want, see [`RingBuf::try_push`] or
247    /// [`RingBuf::push_unchecked`].
248    pub fn push(&mut self, x: T) {
249        assert!(self.try_push(x).is_ok(), "ringbuf is full");
250    }
251
252    /// Pushes (or enqueues) an element on the ring buffer
253    ///
254    /// # Safety
255    /// This does not check if it is out of bounds, which may cause data to be overwritten
256    pub unsafe fn push_unchecked(&mut self, x: T) {
257        self.buf[self.head].write(x);
258        self.head = (self.head + 1) % N;
259    }
260
261    /// Pops (or dequeues) an element off the ring buffer
262    ///
263    /// Returns none if the ringbuf is empty
264    #[must_use]
265    pub fn pop(&mut self) -> Option<T> {
266        if self.is_empty() {
267            return None;
268        }
269
270        // SAFETY: The element at `self.tail` is initialized because the buffer
271        // is not empty (head != tail), and all elements between tail and head
272        // were written by previous push operations.
273        let x = unsafe { self.buf[self.tail].assume_init_read() };
274        self.buf[self.tail] = MaybeUninit::uninit();
275        self.tail = (self.tail + 1) % N;
276        Some(x)
277    }
278}
279
280#[cfg(all(test, feature = "std"))]
281mod tests {
282    use super::*;
283
284    #[test]
285    fn test_is_empty() {
286        let buf = RingBuf::<u8, 1024>::new();
287        assert!(buf.is_empty());
288    }
289
290    #[test]
291    fn test_push() {
292        let mut buf = RingBuf::<u8, 1024>::new();
293        buf.try_push(15).unwrap();
294        buf.try_push(42).unwrap();
295        assert_eq!(unsafe { buf.buf[0].assume_init_read() }, 15);
296        assert_eq!(unsafe { buf.buf[1].assume_init_read() }, 42);
297    }
298
299    #[test]
300    fn test_push_rollover() {
301        let mut buf = RingBuf::<u8, 1024>::new();
302        assert_eq!(buf.max_capacity(), 1023);
303        for i in 0..1023 {
304            let res = buf.try_push((i % 255) as u8);
305            assert!(res.is_ok());
306        }
307        assert!(buf.try_push(1).is_err());
308        assert_eq!(buf.len(), 1023);
309
310        // Now we pop one
311        let res = buf.pop();
312        assert!(res.is_some());
313        assert_eq!(res.unwrap(), 0);
314        assert_eq!(buf.len(), 1022);
315        let res = buf.try_push(1);
316        assert!(res.is_ok());
317        assert_eq!(buf.len(), 1023);
318        assert!(buf.is_full());
319    }
320
321    #[test]
322    fn pop_empty() {
323        let mut buf = RingBuf::<u8, 8>::new();
324        assert_eq!(buf.pop(), None);
325    }
326
327    #[test]
328    fn push_pop_fifo_order() {
329        let mut buf = RingBuf::<u8, 8>::new();
330        buf.push(1);
331        buf.push(2);
332        buf.push(3);
333        assert_eq!(buf.pop(), Some(1));
334        assert_eq!(buf.pop(), Some(2));
335        assert_eq!(buf.pop(), Some(3));
336        assert_eq!(buf.pop(), None);
337    }
338
339    #[test]
340    fn len_tracking() {
341        let mut buf = RingBuf::<u8, 8>::new();
342        assert_eq!(buf.len(), 0);
343        buf.push(1);
344        assert_eq!(buf.len(), 1);
345        buf.push(2);
346        assert_eq!(buf.len(), 2);
347        let _ = buf.pop();
348        assert_eq!(buf.len(), 1);
349    }
350
351    #[test]
352    fn is_full_check() {
353        let mut buf = RingBuf::<u8, 4>::new();
354        assert!(!buf.is_full());
355        buf.push(1);
356        buf.push(2);
357        buf.push(3);
358        assert!(buf.is_full());
359    }
360
361    #[test]
362    fn max_capacity_value() {
363        let buf = RingBuf::<u8, 8>::new();
364        assert_eq!(buf.max_capacity(), 7);
365    }
366
367    #[test]
368    fn wrap_around_multiple_times() {
369        let mut buf = RingBuf::<u8, 4>::new();
370        // Capacity is 3. Do multiple wrap-arounds.
371        for round in 0u8..5 {
372            buf.push(round * 3);
373            buf.push(round * 3 + 1);
374            buf.push(round * 3 + 2);
375            assert_eq!(buf.pop(), Some(round * 3));
376            assert_eq!(buf.pop(), Some(round * 3 + 1));
377            assert_eq!(buf.pop(), Some(round * 3 + 2));
378            assert!(buf.is_empty());
379        }
380    }
381
382    #[test]
383    #[should_panic(expected = "ringbuf is full")]
384    fn push_panics_when_full() {
385        let mut buf = RingBuf::<u8, 4>::new();
386        buf.push(1);
387        buf.push(2);
388        buf.push(3);
389        buf.push(4); // should panic
390    }
391}