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:

  1. 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.
  2. If all operations are blocked and there is a default arm, then the code in the default arm is executed.
  3. If all operations are blocked and there is no default arm, then Select will subscribe to all channels involved. Select will be notified when state in one of the channels has changed. This will wake Select 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!.