select_loop!() { /* proc-macro */ }Expand description
Convenience macro to continuously select! over a set of futures in biased order, with a required shutdown handler.
This macro automatically creates a shutdown future from the provided context and requires a shutdown handler block. The shutdown future is created outside the loop, allowing it to persist across iterations until shutdown is signaled. The shutdown branch is always checked first (biased).
After the shutdown block is executed, the loop breaks by default. If different control flow is desired (such as returning from the enclosing function), it must be handled explicitly.
§Syntax
commonware_macros::select_loop! {
context,
on_start => { /* optional: runs at start of each iteration */ },
on_stopped => { cleanup },
pattern = future => body,
Some(x) = future else break => body, // refutable pattern with else clause
// ...
on_end => { /* optional: runs after select, skipped on shutdown/break/return/continue */ },
}The order of blocks matches execution order:
on_start(optional) - Runs at the start of each loop iteration, before the select. Can usecontinueto skip the select orbreakto exit the loop.on_stopped(required) - The shutdown handler, executed when shutdown is signaled.- Select arms - The futures to select over.
on_end(optional) - Runs after select completes. Skipped on shutdown or if an arm usesbreak/return/continue. Useful for per-iteration post-processing.
All blocks share the same lexical scope within the loop body. Variables declared in
on_start are visible in the select arms, on_stopped, and on_end. This allows
preparing state in on_start and using it throughout the iteration.
The shutdown variable (the future from context.stopped()) is accessible in the
shutdown block, allowing explicit cleanup such as drop(shutdown) before breaking or returning.
§Refutable Patterns with else
For refutable patterns (patterns that may not match), use the else clause to specify
what happens when the pattern fails to match. This uses Rust’s let-else syntax internally:
// Option handling
Some(msg) = rx.recv() else break => { handle(msg); }
Some(msg) = rx.recv() else return => { handle(msg); }
Some(msg) = rx.recv() else continue => { handle(msg); }
// Result handling
Ok(value) = result_stream.recv() else break => { process(value); }
// Enum variants
MyEnum::Data(x) = stream.recv() else continue => { use_data(x); }This replaces the common pattern:
// Before
msg = mailbox.recv() => {
let Some(msg) = msg else { break };
// use msg
}
// After
Some(msg) = mailbox.recv() else break => {
// use msg directly
}§Expansion
The macro expands to roughly the following code:
// Input:
select_loop! {
context,
on_start => { start_code },
on_stopped => { shutdown_code },
pattern = future => { body },
Some(msg) = rx.recv() else break => { handle(msg) },
on_end => { end_code },
}
// Expands to:
{
let mut shutdown = context.stopped();
loop {
// on_start runs at the beginning of each iteration
{ start_code }
select_biased! {
// Shutdown branch (always first due to biased select)
_ = &mut shutdown => {
{ shutdown_code }
break; // on_end is NOT executed on shutdown
},
// Regular pattern branch
pattern = future => {
{ body }
},
// Refutable pattern with else clause (uses let-else)
__select_result = rx.recv() => {
let Some(msg) = __select_result else { break };
{ handle(msg) }
},
}
// on_end runs after select completes (skipped on shutdown/break/return/continue)
{ end_code }
}
}§Example
use commonware_macros::select_loop;
async fn run(context: impl commonware_runtime::Spawner, mut receiver: Receiver<Message>) {
let mut counter = 0;
commonware_macros::select_loop! {
context,
on_start => {
// Prepare state for this iteration (visible in arms and on_end)
let start_time = std::time::Instant::now();
counter += 1;
},
on_stopped => {
println!("shutting down after {} iterations", counter);
drop(shutdown);
},
// Refutable pattern: breaks when channel closes (None)
Some(msg) = receiver.recv() else break => {
println!("received: {:?}", msg);
},
on_end => {
// Access variables from on_start
println!("iteration took {:?}", start_time.elapsed());
},
}
}