Skip to main content

selectables/
lib.rs

1//! # selectables
2//!
3//! Lock-free channels with a unified recv-arm selection model.
4//!
5//! The crate provides multiple channel flavors that can be selected with the same
6//! `recv`-based protocol, including non-blocking and timed fallback behavior.
7//!
8//! ## Channel modules
9//! - [unbounded_mpmc] lock-free unbounded multi-producer, multi-consumer channel.
10//! - [bounded_mpmc] lock-free bounded multi-producer, multi-consumer channel.
11//! - [bounded_mpsc] lock-free bounded multi-producer, single-consumer channel.
12//! - [unbounded_mpsc] lock-free unbounded multi-producer, single-consumer channel.
13//! - [bounded_broadcast] bounded multi-producer, multi-receiver broadcast channel with per-receiver
14//!   lag detection and independent cursors.
15//! - [oneshot] single-send/single-delivery channel compatible with recv select arms.
16//! - [watch] latest-value broadcast channel with versioned change notifications.
17//! - [rendezvous] zero-buffer synchronous handoff channel.
18//!
19//! ## Selection model
20//! - [select!] supports `recv(rx) -> msg => { ... }` and `send(tx, val) -> res => { ... }` arms.
21//! - Non-blocking fallback: `default => { ... }`.
22//! - Timed fallback: `default(duration) => { ... }`.
23//! - Low-level builder API is provided by [Select] and [SelectedOperation].
24//!
25//! ## Timers and disabled arms
26//! - [bounded_mpmc::after] and [bounded_mpsc::after] create one-shot timer receivers.
27//! - [bounded_mpmc::never] and [bounded_mpsc::never] create permanently non-firing receivers.
28//! - [interval::interval] and [interval::interval_at] create repeating timer receivers.
29//!
30//! ## Error model
31//! - [RecvError] for blocking receive/completion failures. Includes `Lagged { skipped }` for
32//!   bounded broadcast channels when a receiver falls behind senders.
33//! - [error::TryRecvError] for non-blocking receive attempts.
34//! - [SendError] for failed sends that return ownership of the message.
35//!
36//! ## Lag handling (broadcast channels only)
37//! When using [bounded_broadcast], receivers may encounter `Lagged { skipped }` if they fall
38//! behind the senders. This is normal and indicates the ring buffer wrapped around before the
39//! receiver caught up. Recommended handling:
40//!
41//! ```text
42//! match rx.recv() {
43//!     Ok(msg) => process(msg),
44//!     Err(RecvError::Lagged { skipped }) => {
45//!         log::warn!("Receiver lagged by {} messages; recovered", skipped);
46//!         // Receiver cursor was automatically advanced; next recv will get oldest available
47//!     }
48//!     Err(RecvError::Disconnected) => {
49//!         log::info!("All senders disconnected");
50//!         break;
51//!     }
52//! }
53//! ```
54//!
55//! See [bounded_broadcast] module docs for detailed lag recovery patterns.
56//!
57//! ## Quick example
58//! ```
59//! use std::time::Duration;
60//! use selectables::{Select, bounded_mpmc, select};
61//!
62//! let (tx, rx) = bounded_mpmc::channel::<i32>(1);
63//! tx.send(7).unwrap();
64//!
65//! select! {
66//!     recv(rx) -> msg => assert_eq!(msg, Ok(7)),
67//!     default(Duration::from_millis(10)) => panic!("unexpected timeout"),
68//! }
69//!
70//! let mut sel = Select::new();
71//! let i = sel.recv(bounded_mpmc::never::<i32>());
72//! let _ = i;
73//! ```
74
75use std::sync::Arc;
76use std::sync::atomic::AtomicUsize;
77
78#[macro_use]
79mod internals;
80mod error;
81mod select;
82mod waiter;
83
84pub mod bounded_broadcast;
85pub mod bounded_mpmc;
86pub mod bounded_mpsc;
87pub mod interval;
88pub mod oneshot;
89pub mod rendezvous;
90pub mod unbounded_mpmc;
91pub mod unbounded_mpsc;
92pub mod watch;
93
94pub use error::{RecvError, SendError};
95pub use select::{Select, SelectedOperation};
96
97/// Trait for receivers that can participate in `recv` arms of [`select!`].
98///
99/// The select protocol is a four-phase algorithm: **try → register → park → complete**.
100/// Each phase is represented by one or more methods on this trait.
101pub trait SelectableReceiver {
102    /// The value type produced by a successful receive.
103    type Output;
104    /// `true` when a value is immediately available (try phase).
105    ///
106    /// Returns `true` if the channel is disconnected as well, so that
107    /// [`complete`](Self::complete) can return the appropriate error right away.
108    fn is_ready(&self) -> bool;
109    /// Register a waiter for the select park phase.
110    ///
111    /// `case_id` identifies this arm among all arms in the `select!` call.
112    /// `selected` is the shared atomic that the first arm to fire sets to its `case_id`.
113    fn register_select(&self, case_id: usize, selected: Arc<AtomicUsize>);
114    /// Remove the previously registered waiter (losing-arm cleanup).
115    ///
116    /// Called on every arm that did *not* win the selection so that no dangling
117    /// waker references remain in the channel's waiter list.
118    fn abort_select(&self, selected: &Arc<AtomicUsize>);
119    /// Consume one value after winning the selection (complete phase).
120    ///
121    /// Returns `Err(RecvError::Disconnected)` if the channel is empty and all
122    /// senders have been dropped.
123    fn complete(&self) -> Result<Self::Output, RecvError>;
124}
125
126/// Trait for senders that can participate in `send` arms of `select!`.
127///
128/// `is_ready` returns `true` when a send can complete without blocking (buffer
129/// has space or the channel is disconnected). The select protocol uses the same
130/// four-phase algorithm as for receivers: try → register → park → complete.
131pub trait SelectableSender {
132    /// The value type this sender accepts.
133    type Input: Send;
134    /// `true` when a send would complete immediately (buffer not full, or
135    /// all receivers dropped — in which case `complete_send` returns `Err`).
136    fn is_ready(&self) -> bool;
137    /// Register a send-side waiter for the select park phase.
138    fn register_select(&self, case_id: usize, selected: Arc<AtomicUsize>);
139    /// Remove the previously registered waiter (losing arm cleanup).
140    fn abort_select(&self, selected: &Arc<AtomicUsize>);
141    /// Execute the send after winning the selection.
142    /// Returns `Err(SendError(val))` if all receivers have been dropped.
143    fn complete_send(&self, value: Self::Input) -> Result<(), SendError<Self::Input>>;
144}