chan::chan_select!
[−]
[src]
macro_rules! chan_select { ($select:ident, default => $default:expr, $( $chan:ident.$meth:ident($($send:expr)*) $(-> $name:pat)* => $code:expr, )+) => { chan_select!( $select, default => $default, $($chan.$meth($($send)*) $(-> $name)* => $code),+); }; ($select:ident, default => $default:expr, $( $chan:ident.$meth:ident($($send:expr)*) $(-> $name:pat)* => $code:expr ),+) => {{ let mut sel = &mut $select; $(let $chan = sel.$meth(&$chan $(, $send)*);)+ let which = sel.try_select(); $(if which == Some($chan.id()) { $(let $name = $chan.into_value();)* $code } else)+ { $default } }}; ($select:ident, $( $chan:ident.$meth:ident($($send:expr)*) $(-> $name:pat)* => $code:expr, )+) => { chan_select!( $select, $($chan.$meth($($send)*) $(-> $name)* => $code),+); }; ($select:ident, $( $chan:ident.$meth:ident($($send:expr)*) $(-> $name:pat)* => $code:expr ),+) => {{ let mut sel = &mut $select; $(let $chan = sel.$meth(&$chan $(, $send)*);)+ let which = sel.select(); $(if which == $chan.id() { $(let $name = $chan.into_value();)* $code } else)+ { unreachable!() } }}; (default => $default:expr) => {{ $default }}; (default => $default:expr,) => {{ $default }}; ($select:ident, default => $default:expr) => {{ $default }}; ($select:ident, default => $default:expr,) => {{ $default }}; ($select:ident) => {{ let mut sel = &mut $select; sel.select(); // blocks forever }}; () => {{ let mut sel = $crate::Select::new(); chan_select!(sel); }}; ($($tt:tt)*) => {{ let mut sel = $crate::Select::new(); chan_select!(sel, $($tt)*); }}; }
Synchronize on at most one channel send or receive operation.
This is a heterogeneous select. Namely, it supports any mix of asynchronous, synchronous or rendezvous channels, any mix of send or receive operations and any mix of types on channels.
Here is how select operates:
- It first examines all send and receive operations. If one or more of them can succeed without blocking, then it randomly selects one, executes the operation and runs the code in the corresponding arm.
- If all operations are blocked and there is a
default
arm, then the code in thedefault
arm is executed. - If all operations are blocked and there is no
default
arm, thenSelect
will subscribe to all channels involved.Select
will be notified when state in one of the channels has changed. This will wakeSelect
up, and it will retry the steps in (1). If all operations remain blocked, then (3) is repeated.
Example
Which one synchronizes first?
use std::thread; let (asend, arecv) = chan::sync(0); let (bsend, brecv) = chan::sync(0); thread::spawn(move || asend.send(5)); thread::spawn(move || brecv.recv()); chan_select! { arecv.recv() -> val => { println!("arecv received: {:?}", val); }, bsend.send(10) => { println!("bsend sent"); }, }
See the "failure modes" section below for more examples of the syntax.
Example: empty select
An empty select, chan_select! {}
will block indefinitely.
Warning
chan_select!
is simultaneously the most wonderful and horrifying thing
in this crate.
It is wonderful because it is essential for the composition of channel operations in a concurrent program. Without select, channels becomes much less expressive.
It is horrifying because the macro used to define it is extremely sensitive. My hope is that it is simply my own lack of creativity at fault and that others can help me fix it, but we may just be fundamentally stuck with something like this until a proper compiler plugin can rescue us.
Failure modes
When I say that this macro is sensitive, what I mean is, "if you misstep on the syntax, you will be slapped upside the head with an irrelevant error message."
Consider this:
chan_select! { default => { println!(" ."); thread::sleep_ms(50); } tick.recv() => println!("tick."), boom.recv() => { println!("BOOM!"); return; }, }
The compiler will tell you that the "recursion limit reached while expanding the macro."
The actual problem is that every arm requires a trailing comma,
regardless of whether the arm is wrapped in a { ... }
or not. So it
should be written default => { ... },
. (I'm told that various highly
skilled individuals could remove this restriction.)
Here's another. Can you spot the problem? I swear it's not commas this time.
chan_select! { tick.recv() => println!("tick."), boom.recv() => { println!("BOOM!"); return; }, default => { println!(" ."); thread::sleep_ms(50); }, }
This produces the same "recursion limit" error as above.
The actual problem is that the default
arm must come first (or it must
be omitted completely).
Yet another:
chan_select! { default => { println!(" ."); thread::sleep_ms(50); }, tick().recv() => println!("tick."), boom.recv() => { println!("BOOM!"); return; }, }
Again, you'll get the same "recursion limit" error.
The actual problem is that the channel operations must be of the form
ident.recv()
or ident.send()
. You cannot use an arbitrary expression
in place of ident
that evaluates to a channel! To fix this, you must
rebind tick()
to an identifier outside of chan_select!
.