Systems Programming

On this page 6

Systems code lives with constraints that most application code never meets: a fixed latency budget, a memory ceiling, a machine you have to address directly. Home is built for that layer, and tries to make it readable while it is there.

What Home gives you here

No collector, no runtime. Ownership and borrowing settle every lifetime at compile time. There is no collector to pause your process and nothing to ship alongside the binary. See the memory model.

Native code through LLVM. home build produces a native executable. The optimiser sees monomorphized generics and comptime-resolved constants, not a dynamic dispatch table.

Errors as values. Nothing unwinds the stack behind your back. A function that can fail says so in its type, and ? propagates without hiding control flow. See error handling.

Direct memory access when you need it. Pointers, slices, alignment control and inline assembly are available, and are the exception rather than the texture of ordinary code.

What it looks like

A bounded ring buffer, the kind of structure that shows up in every systems codebase:

struct Ring<T> {
  items: []T,
  head: int,
  tail: int,
  len: int,
}

impl<T> Ring<T> {
  fn with_capacity(cap: int): Ring<T> {
    Ring { items: Array.with_capacity(cap), head: 0, tail: 0, len: 0 }
  }

  fn push(mut self, value: T): Result<(), Full> {
    if (self.len == self.items.len()) {
      return Err(Full)
    }

    self.items[self.tail] = value
    self.tail = (self.tail + 1) % self.items.len()
    self.len += 1
    Ok(())
  }

  fn pop(mut self): Option<T> {
    match self.len {
      0 => None,
      _ => {
        let value = self.items[self.head]
        self.head = (self.head + 1) % self.items.len()
        self.len -= 1
        Some(value)
      }
    }
  }
}

The capacity check returns a value rather than trapping, the empty case is handled by the match rather than by a comment, and neither costs anything at runtime that a hand-written C version would not also pay.

Compile-time work

Anything you can compute before the program runs, you can compute in comptime. Lookup tables, protocol tables and dispatch tables become constants in the binary:

comptime {
  let crc_table = build_crc_table()
}

The table exists in the compiled output. No initialisation runs at start-up, and nothing has to be lazily built on first use.

Talking to C

Existing systems code is written in C, and Home calls it without a binding generator:

extern "C" {
  fn clock_gettime(clock: int, ts: *TimeSpec): int
}

See FFI for struct layout, callbacks and ownership across the boundary.

Status

The front end, type inference and the interpreter are usable today. Native code generation goes through LLVM and handles single-entrypoint builds; whole-module-graph bundling and cross-target builds are still in progress. Check the capability matrix before committing a project to a specific feature.

Released under the MIT License.