Examples

Real-world usage patterns for ring-buffer-macro. Each example is a complete, working program.

Logging Buffer

A fixed-size log that drops oldest entries when full. Useful for embedded systems or any context where you want bounded memory usage for logs.

1use ring_buffer_macro::ring_buffer;
2
3#[ring_buffer(1024)]
4struct LogBuffer(String);
5
6fn main() {
7 let mut logs = LogBuffer::new();
8
9 for i in 0..2000 {
10 // Drop oldest entry if full
11 if logs.is_full() {
12 logs.dequeue();
13 }
14 logs.enqueue(format!("[{}] Event occurred", i)).unwrap();
15 }
16
17 // Only the last 1024 logs remain
18 println!("Log count: {}", logs.len());
19
20 for log in logs.iter() {
21 println!("{}", log);
22 }
23}

Audio Sample Buffer

Ring buffers are the standard data structure for real-time audio processing. The SPSC mode is ideal: one thread captures audio, another processes it.

1use ring_buffer_macro::ring_buffer;
2use std::sync::Arc;
3use std::thread;
4
5#[ring_buffer(capacity = 4096, mode = "spsc")]
6struct AudioBuffer(f32);
7
8fn main() {
9 let buf = Arc::new(AudioBuffer::new());
10 let (producer, consumer) = buf.split();
11
12 // Audio input thread
13 let input = thread::spawn(move || {
14 for i in 0..44100 {
15 let sample = (i as f32 * 0.01).sin();
16 while producer.try_enqueue(sample).is_err() {
17 std::hint::spin_loop();
18 }
19 }
20 });
21
22 // Audio output thread
23 let output = thread::spawn(move || {
24 let mut processed = 0;
25 while processed < 44100 {
26 if let Some(sample) = consumer.try_dequeue() {
27 // Apply gain, filter, etc.
28 let _output = sample * 0.8;
29 processed += 1;
30 }
31 }
32 });
33
34 input.join().unwrap();
35 output.join().unwrap();
36}

Rate Limiter

Track timestamps of recent requests using a ring buffer as a sliding window. When the window is full, reject new requests until old ones expire.

1use ring_buffer_macro::ring_buffer;
2use std::time::{Duration, Instant};
3
4#[ring_buffer(100)]
5struct RequestLog(Instant);
6
7struct RateLimiter {
8 log: RequestLog,
9 window: Duration,
10}
11
12impl RateLimiter {
13 fn new(max_requests: usize, window: Duration) -> Self {
14 // Note: max_requests should match the buffer capacity
15 Self {
16 log: RequestLog::new(),
17 window,
18 }
19 }
20
21 fn allow(&mut self) -> bool {
22 let now = Instant::now();
23
24 // Remove expired entries from the front
25 while let Some(&oldest) = self.log.peek() {
26 if now.duration_since(oldest) > self.window {
27 self.log.dequeue();
28 } else {
29 break;
30 }
31 }
32
33 // Check if we have capacity
34 if self.log.is_full() {
35 return false;
36 }
37
38 self.log.enqueue(now).unwrap();
39 true
40 }
41}
42
43fn main() {
44 let mut limiter = RateLimiter::new(100, Duration::from_secs(60));
45
46 for _ in 0..150 {
47 if limiter.allow() {
48 println!("Request allowed");
49 } else {
50 println!("Rate limited");
51 }
52 }
53}

Multi-Producer Event System

Use MPSC mode to collect events from multiple producer threads into a single consumer that processes them sequentially.

1use ring_buffer_macro::ring_buffer;
2use std::sync::Arc;
3use std::thread;
4
5#[ring_buffer(capacity = 256, mode = "mpsc")]
6struct EventBus(String);
7
8fn main() {
9 let bus = Arc::new(EventBus::new());
10 let consumer = bus.consumer();
11
12 // Spawn multiple event producers
13 let handles: Vec<_> = (0..4).map(|id| {
14 let producer = bus.producer();
15 thread::spawn(move || {
16 for i in 0..10 {
17 let event = format!("producer-{}: event-{}", id, i);
18 while producer.try_enqueue(event.clone()).is_err() {
19 std::hint::spin_loop();
20 }
21 }
22 })
23 }).collect();
24
25 // Single consumer processes all events
26 let mut count = 0;
27 while count < 40 {
28 if let Some(event) = consumer.try_dequeue() {
29 println!("{}", event);
30 count += 1;
31 }
32 }
33
34 for h in handles {
35 h.join().unwrap();
36 }
37}

Moving Average Calculator

Compute a running average over the last N values. The ring buffer naturally maintains a fixed window of samples.

1use ring_buffer_macro::ring_buffer;
2
3#[ring_buffer(10)]
4struct SampleWindow(f64);
5
6struct MovingAverage {
7 window: SampleWindow,
8}
9
10impl MovingAverage {
11 fn new() -> Self {
12 Self { window: SampleWindow::new() }
13 }
14
15 fn push(&mut self, value: f64) {
16 if self.window.is_full() {
17 self.window.dequeue();
18 }
19 self.window.enqueue(value).unwrap();
20 }
21
22 fn average(&self) -> Option<f64> {
23 if self.window.is_empty() {
24 return None;
25 }
26 let sum: f64 = self.window.iter().sum();
27 Some(sum / self.window.len() as f64)
28 }
29}
30
31fn main() {
32 let mut avg = MovingAverage::new();
33
34 let readings = [23.1, 23.4, 23.2, 24.0, 23.8, 23.5,
35 23.9, 24.1, 23.7, 23.6, 24.2, 24.5];
36
37 for reading in readings {
38 avg.push(reading);
39 println!("Reading: {:.1}, Average: {:.2}",
40 reading, avg.average().unwrap());
41 }
42}