Skip to content
You are reading next, which describes the unreleased compiler. Khora is unstable before v1: syntax, standard-library APIs and behavior may change. The current release is documented under v0.4.

std::clock

What time it is, and waiting for it to be later.

Its own module, and it did not start as one. Clock lived in std::env because both answer “what did the outside world hand this process”, and that grouping cost something the moment anything else wanted a clock: env_native.kh is native-only for getenv and argv, so the clock was native-only too – not because clocks are unportable, but because of the file it was in. A Worker has Date.now. Splitting the two leaves one small file with one reason to carry _native, and a clock_wasm.kh is then an afternoon rather than an untangling.

It also puts the clock where the things that need one can import it: std::resilience backs off, and every future timeout and deadline is the same dependency. Importing std::env to get a clock was a line nobody could explain.

The C side is not ISO C, and that is why these are runtime exports rather than extern bindings to libc. Milliseconds are GetSystemTimeAsFileTime on Windows and clock_gettime on Unix – two calls, two epochs, two headers – and ISO C’s time is whole seconds with nothing finer spelled the same everywhere. The runtime already knows which target it was built for.

Effects: Clock

Methods: Clock

pub effect Clock {
unix_seconds: () -> Int,
unix_millis: () -> Int,
monotonic_millis: () -> Int,
sleep: (Int) -> (),
}

What time it is.

A capability rather than plain functions, and separate from Env because it is a different thing to deny: a library that may read a configuration variable is not thereby a library that may know how long something took, and a test that pins one usually wants to pin the other differently.

Two clocks, not one, and the split is not a detail. A wall clock says what time it is and can jump: NTP steps it, an administrator sets it, a virtual machine resumes with a stale one – and it can jump backwards, so subtracting two readings of it is not a duration, it is a guess. A monotonic clock cannot go backwards and is what “how long did this take” actually wants; its zero is arbitrary, so it can never be a timestamp.

One millis would therefore have been the wrong one about half the time, and the half it was wrong for would be the half that only fails in production, at 2am, when the clock is stepped an hour. Naming both makes the caller say which they meant.

unix_seconds: () -> Int

Seconds since 1970, on the wall clock.

Kept, because it is what a caller who wants a date wants, and it is derived from the same source as unix_millis so the two can never disagree about what second it is.

unix_millis: () -> Int

Milliseconds since 1970, on the wall clock. A timestamp, not a duration.

monotonic_millis: () -> Int

Milliseconds on a clock that only goes forwards, counted from the first time anything in the process asked.

The one to subtract. Two readings are always ordered and their difference is always a real elapsed time — which is exactly what unix_millis cannot promise.

sleep: (Int) ->()

Waits this many milliseconds before going on.

An operation on the clock, and that is the whole design. Waiting is the one thing a program does that a test cannot afford to actually do, and putting it here means a fake clock is an ordinary handler – no test runtime, no special mode, no rule about which fiber may advance time. The alternative is what every language with an ambient sleep ends up building: a parallel clock the runtime knows about, and documentation that has to open by warning you to fork the sleeping code or deadlock.

The capability is the seam, which is the argument Random::seeded already makes about the other unrepeatable input.

Zero or less returns at once. A caller who meant “let somebody else run” wants a safepoint, and every loop back-edge already has one — and every back-edge is a cancellation point too, so a loop { sleep(short) } worker stops when it is asked to.

A sleep is a cancellation point. A fiber inside sleep is woken by a cancellation on either fiber backend and stops there; the statement after the sleep does not run.

impl Clock
pub fn real() -> Clock

The real clock.

Seconds are milliseconds divided down rather than a second call to ISO C’s time, so that a program reading both never sees them straddle a tick.

Khora 0.4.0built frombd964738afb6on 2026-10-17 20:41ZVersioningSecurityContributingReleasesSource