std/timer
One-shot timers, repeating tickers, and timeouts.
from "std/timer" import { Timer, Ticker, recvTimeout, waitReadable, waitWritable }Everything here is a thin layer over machinery that already exists: a green task (std/runtime), a Channel (std/sync), and a Select timer arm (std/select). A Timer or Ticker is a task that sleeps and sends, so it composes with selectRecv like any other channel.
Scheduler
Timer and fd arms are driven by the green scheduler's poll loop. std/select documents the trap: on a program that never spawned a green task there is no scheduler, so sel.onTimeout is armed and never fires — it looks like protection and isn't. Every entry point in std/timer calls ensureTimersLive() first, so a timeout here is honest on the main context too.
Timer
The clock starts at Timer.after, not at the first recv.
var t = Timer.after(Duration.secs(5))
if t.recv().isSome() {
print("deadline reached")
}Ticker
A slow receiver does not pile up a backlog of ticks; stop() takes effect at the ticker's next wake.
var tk = Ticker.every(Duration.millis(100))
var sel = Select.new()
selectRecv(sel, tk.channel())
selectRecv(sel, work)
match sel.wait() {
0 => { print("tick") }
1 => { /* work arrived */ }
}
tk.stop()Timeouts
recvTimeout is a free function, not a method, for the same reason selectRecv is one. waitReadable and waitWritable are the timeout under a blocking read or write: check first, then do the IO knowing it will not park forever.
if !waitReadable(conn, Duration.secs(10)) {
print("client idle, closing")
}API reference
Ticker
pub struct TickerA periodic timer. Dropping it stops it.
Ticker.channel
fn Ticker.channel(self: &Ticker): Channel<Instant>The tick channel, for arming in a Select.
Ticker.every
fn Ticker.every(period: &Duration): TickerDeliver an Instant every period. A slow receiver does not accumulate a backlog: the buffer holds one tick and the sender drops any it can't deposit, which is Go's behavior and the only one that can't grow without bound.
A period of zero or less would be a hot loop, so it yields a Ticker that never fires.
Ticker.recv
fn Ticker.recv(self: &Ticker): Option<Instant>Block for the next tick. None once the ticker is stopped and drained.
Ticker.stop
fn Ticker.stop(self: &Ticker): voidStop ticking. The task exits at its next wake, so up to one period later. Idempotent.
Timer
pub struct TimerA one-shot timer. Dropping it cancels it: otherwise an unfired timer's task would sleep out the full span and send into a channel nobody holds.
Timer.after
fn Timer.after(d: &Duration): TimerStart a timer that delivers one Instant after d. The clock starts now, not at the first recv.
Timer.channel
fn Timer.channel(self: &Timer): Channel<Instant>The delivery channel, for arming in a Select alongside other sources.
Timer.recv
fn Timer.recv(self: &Timer): Option<Instant>Block until the timer fires. None if it was stopped first.
Timer.stop
fn Timer.stop(self: &Timer): voidCancel. Idempotent; a timer that already fired is unaffected (its value is still waiting in the channel).
Functions
recvTimeout
pub fn recvTimeout<T>(ch: &Channel<T>, d: &Duration): Option<T>Receive from ch, giving up after d. None means the timeout won or the channel closed empty.
A free function, not a method: Channel lives in std/sync, and an impl Channel<T> here would be visible only to programs that already import something from std/timer. A free function names its dependency.
waitReadable
pub fn waitReadable<T: AsFd>(h: &T, d: &Duration): boolWait until h is readable, or d elapses. False on timeout.
This is the timeout under a blocking read: check first, then read knowing the read won't park forever. It reads nothing itself.
waitWritable
pub fn waitWritable<T: AsFd>(h: &T, d: &Duration): boolWait until h accepts a write without blocking, or d elapses.