Concurrency
Lisette follows Go’s concurrency model, where goroutines communicate via channels.
A task (goroutine) spawns concurrent work.
task long_running_job()
task { let records = fetch_records() write_report(records)}Channel
Section titled “Channel”A Channel carries values between tasks.
let ch = Channel.new<int>()
task { let value = heavy_computation() ch.send(value)}
if let Some(v) = ch.receive() {value sent by task fmt.Println(v)}
ch.close()You can split a channel into sender and receiver:
let ch = Channel.new<int>()let (tx, rx) = ch.split()tx is a Sender<int>, rx a Receiver<int>
task tx.send(42)
match rx.receive() { Some(v) => fmt.Println(v), None => fmt.Println("channel closed"),}For signaling without a value, use Channel<()>:
let done = Channel.new<()>()
task { rebuild_index() done.send(())}
done.receive()blocks until task signals completionA Channel can be iterated until closed:
let ch = Channel.buffered<int>(3)ch.send(1)ch.send(2)ch.send(3)ch.close()
for v in ch { fmt.Println(v)prints 1, 2, 3}A Receiver<T> is iterable in the same way.
select
Section titled “select”select waits on several channel operations and runs whichever is ready first:
let result = select { match ch1.receive() { Some(v) => v, None => 0, }, match ch2.receive() { Some(v) => v * 2, None => 0, },}A shorthand receive arm destructures the Some case:
let result = select { let Some(v) = ch.receive() => v, _ => 0,}The _ arm runs immediately when no operation is ready, which makes the select non-blocking. It also catches the closed case for a shorthand receive arm.
A send arm in a select runs when the send completes:
select { ch.send(42) => fmt.Println("sent"), _ => fmt.Println("channel full"),}A send arm can panic with send on closed channel, unlike ch.send() outside a select, which returns false. Wrap the select in a recover block to guard against this.