LEVIATHAN v962456e · 962456eee1

Library

The native floor — std::sys*

What the sys* functions in std are, why compile-time code cannot call them, and which two are public.

since 0.1.0-alpha.1linuxwindowswasm

Description

Everything a program does to the outside world ends in a native: a function the runtime provides, declared in the standard library without a body. Reading a line, opening a file, starting a timer, connecting a socket and spawning a child process all bottom out in one. These functions live in namespace std and every one of them has a name starting with sys (std::sysWrite, std::sysOpen, std::sysTcpConnect, and so on). Together they are the native floor.

The floor is deliberately plain. Its functions take and return integers, strings and arrays; results that can fail use -1 or None; and the families it covers are

  • console and files: reading and writing descriptors, opening, stat, directories;
  • time: the wall clock, a monotonic clock, and timers;
  • the event loop: read and write watches on descriptors;
  • network: TCP sockets, name resolution, TLS sessions, RSA encryption;
  • processes and terminals: spawning children, pseudo-terminals, raw mode, signals;
  • randomness and the environment: entropy, environment variables;
  • a windowing floor used by the Dagon GUI framework (see lang.gui-floor);
  • threads and tasks: the substrate under std::spawn, TaskGroup and await.

The floor is not the API. Programs are meant to use the types built on top of it: File, Timer, TcpStream, HttpClient, Process, Pty, env, term, signal and the rest of the library. Those wrap the natives, give them types and error behavior, and are the surface that stays stable. Every sys* function other than the two listed below is an implementation detail of the library, not part of the public API. Its name, its arguments, and even its existence can change between releases, and the library documentation hides it.

Rules

  • Two natives are public: std::sysReadLine(fd) and std::sysTimerStart(delayMs, intervalMs, callback). They are the smallest way to read a line of input and to schedule a callback.
  • sysReadLine(fd) returns the next line of the descriptor with the trailing newline removed, and returns "" at the end of input. A blank line is also "", so the function cannot tell a blank line from the end; call it in a loop that you bound by other means if that matters. Descriptor 0 is standard input.
  • sysTimerStart(delayMs, intervalMs, callback) schedules callback(tickNumber) after delayMs milliseconds, and then every intervalMs milliseconds when intervalMs is above zero (0 means one shot). It returns a timer id. To be able to cancel a timer, create it with std::after or std::every and call Timer.cancel(). The callback must be a closure. A callback that is not one (for example a function-typed global that is read before its initializer has run) is rejected when the timer is registered: nothing is scheduled and the call throws a catchable RuntimeException. Timers that fire together run in due-time order, then creation order, and callbacks never run concurrently. Most programs use std::after and std::every instead (see std.Timer).
  • Compile-time code cannot call the floor. A comptime expression is evaluated while the program is being compiled, and a build must never read the clock, the environment, the file system, the network or a terminal. Every sys* function is therefore denied during compile-time evaluation. The compiler stops with the name of the native.
  • Some floors are not available everywhere. For the browser target the windowing floor and the file system functions are rejected at compile time, and child processes (Process) are rejected at compile time for Windows targets. Where a native is unavailable the compiler reports which one instead of producing a program that misbehaves.
  • A function that reports optional results distinguishes absent from empty: None and "" (or []) are different answers.

Examples

Reading input line by line. A blank line and the end of input both read as "":

Reading lines from standard input

string line = std::sysReadLine(0);
console.writeln("first: ${line}");
string second = std::sysReadLine(0);
console.writeln("second: ${second}");
string end = std::sysReadLine(0);
console.writeln("at end: [${end}]");
alpha
beta
first: alpha
second: beta
at end: []

Timers start in due-time order, not in the order they were registered:

Timers fire in due-time order

std::sysTimerStart(20, 0, (n) => console.writeln("later"));
std::sysTimerStart(10, 0, (n) => console.writeln("sooner"));
console.writeln("scheduled");
scheduled
sooner
later

A callback that is not a closure is rejected at registration, and the error can be caught:

A non-closure callback is rejected

try {
    std::sysTimerStart(1, 0, handler);
} catch (RuntimeException e) {
    console.writeln(e.message);
}
(int) => void handler = (n) => console.writeln("tick");
sysTimerStart: callback must be a closure

Compile-time code cannot reach the floor:

comptime int now = std::sysNow();
console.writeln(now);
not run — a compile error on purpose

The compiler rejects this program with comptime code may not perform I/O ('sysNow').

Notes

Functions that take a Block instead of a string (a fixed-length byte buffer) are overloads of the same read and write natives; the overload is chosen by argument types.

See also

  • File — An open file.
  • Timer — A source of ticks on the event loop.
  • env — The running process: its command-line arguments, its environment variables, and how it exits.
  • term — Control of the terminal the program runs in: raw input mode and the window size.
  • signal — Operating-system signals delivered to the program as streams.
  • TcpStream — A connected network socket that reads and writes text.
  • Process — Runs another program as a child process and gives you its standard input, standard output and standard error as streams.