ring-buffer-macro
A proc macro that turns a tuple struct into a fixed-size ring buffer. Supports single-threaded, lock-free SPSC, and lock-free MPSC modes.
[]
= "0.2.0"
How it works
You write a tuple struct with one field indicating the element type:
;
The macro replaces this with a named struct (data, head, tail, etc.) and generates a full impl block with ring buffer methods. The tuple struct is just a way to specify the name and element type — the (i32) field gets thrown away.
For concurrent modes (SPSC/MPSC), the generated struct uses UnsafeCell<Vec<MaybeUninit<T>>> with atomic indices instead of plain Vec<T>, so items are moved rather than cloned. This drops the trait bound from Clone to Send.
Usage
Standard mode
use ring_buffer;
;
Generics
Type parameters are preserved in the generated code:
;
let mut buf: = new;
buf.enqueue.unwrap;
SPSC (lock-free, single-producer/single-consumer)
Uses AtomicUsize head/tail with acquire/release ordering. No locks.
use ring_buffer;
use Arc;
use thread;
;
MPSC (multi-producer, single-consumer)
Producers coordinate via compare_exchange_weak on the tail index. Each slot has an AtomicBool flag so the consumer knows when a write is complete.
The producer handle is Clone; the consumer is not. Only one consumer should exist — this is a protocol constraint, not enforced at runtime.
use ring_buffer;
use Arc;
use thread;
;
Blocking mode
Available for both SPSC and MPSC. Uses a Mutex<()> + Condvar pair — the mutex doesn't protect data, it just satisfies the condvar API. The actual data path is still lock-free atomics.
use ring_buffer;
;
// These wait instead of returning Err/None
// producer.enqueue_blocking("message".to_string());
// let msg = consumer.dequeue_blocking();
Power-of-two optimization
If your capacity is a power of two, this swaps modulo for bitwise AND on index wraparound. The macro enforces the constraint at compile time.
;
Cache-line padding
Aligns head and tail to 64-byte boundaries to prevent false sharing when producer and consumer run on different cores. Mostly relevant for SPSC mode.
;
Configuration
| Option | Values | Default | Description |
|---|---|---|---|
capacity |
positive integer | required | Maximum number of elements |
mode |
"standard", "spsc", "mpsc" |
"standard" |
Buffer mode |
power_of_two |
true, false |
false |
Bitwise indexing (capacity must be 2^n) |
cache_padded |
true, false |
false |
64-byte align head/tail to avoid false sharing |
blocking |
true, false |
false |
Blocking enqueue/dequeue (concurrent modes only) |
// simple
// named
Generated API
Standard mode
new()/enqueue(item)/dequeue()/clear()peek()/peek_mut()/peek_back()iter()/drain()is_full()/is_empty()/len()/capacity()
dequeue() and drain() require T: Clone (bound is on the method, not the struct, so you can create a buffer of non-Clone types — you just can't dequeue from it).
SPSC mode
Buffer: new(), split() -> (Producer, Consumer), is_full(), is_empty(), len(), capacity()
Producer: try_enqueue(item), enqueue_blocking(item) (if blocking)
Consumer: try_dequeue(), dequeue_blocking() (if blocking), peek()
MPSC mode
Buffer: new(), producer(), consumer(), is_empty(), len(), capacity()
Producer (clonable): try_enqueue(item), enqueue_blocking(item) (if blocking), is_full()
Consumer: try_dequeue(), dequeue_blocking() (if blocking), peek(), is_empty(), len()
Requirements
- Input must be a tuple struct with one field:
struct Buffer(i32) - Standard mode requires
T: Clone(for dequeue/drain only) - SPSC/MPSC modes require
T: Send - Capacity must be a positive integer
License
MIT