Low

Low

A compiled, garbage-collected language with green threads, enums and typed errors. It builds small static binaries for Linux and Windows.

enum Shape {
    Circle(radius: i64)
    Rect(w: i64, h: i64)
}

fn area(s: Shape) -> i64 {
    match s {
        Circle(r) => 3 * r * r
        Rect(w, h) => w * h
    }
}

fn main() {
    print(area(Shape.Rect(3, 4)))
}

Install

Linux

$ curl -fsSL https://low.simoncurtis.dev/install.sh | sh

Windows

> irm https://low.simoncurtis.dev/install.ps1 | iex

Needs binutils for as and ld. The Windows target also needs clang and lld-link. Build from source

Use

$ cat hello.low
fn main() {
    print("hello")
}
$ low run hello.low
hello
$ low build hello.low -o hello
$ low build hello.low -target windows/amd64

Every command

Compile-time checks

enum Shape {
    Circle(radius: i64)
    Rect(w: i64, h: i64)
    Empty
}

fn area(s: Shape) -> i64 {
    match s {
        Circle(r) => 3 * r * r
        Rect(w, h) => w * h
    }
}

fn main() {
    print(area(Shape.Empty))
}

shape.low:8:5: missing arms for Empty

enum MathError {
    DivideByZero
}

fn divide(a: i64, b: i64) -> i64 ! MathError {
    if b == 0 { fail DivideByZero }
    a / b
}

fn main() {
    print(divide(7, 0))
}

math.low:11:11: unhandled error MathError: use ? or catch

Benchmarks against Go

LowGo
Binary32 KB2.3 MB
Build18 ms122 ms
Run149 ms163 ms

The fib benchmark, median of three runs. Across nine benchmarks Low runs in 0.44 to 0.98 times Go's time and uses more peak memory on six.

Compilation stages

Source

fn fib(n: i64) -> i64 {
    if n < 2 { n } else {
        fib(n - 1) + fib(n - 2)
    }
}

IR

fn fib params=1 results=1 slots=[]
b0: preds=[]
  v1 = param #0 []
  v2 = const #2 []
  v3 = lt:i64 [v1 v2]
  if v3 then b1 else b2
b1: preds=[b0]
  jump b3

Assembly

fn_fib:
.Lfib_0:
    cmp rdi, 2
    jge .Lfib_2
.Lfib_1:
    mov rax, rdi
    ret

Binary

7f45 4c46 0201 0100
0000 0000 0000 0000
0200 3e00 0100 0000
ba47 4000 0000 0000
4000 0000 0000 0000
787e 0000 0000 0000

Runtime and standard library

fn worker(jobs: chan[i64], results: chan[i64]) {
    for j in jobs { results <- j * j }
}

fn main() {
    let jobs = chan[i64](16)
    let results = chan[i64](16)
    for _ in 0..4 { spawn worker(jobs, results) }
    for i in 1..11 { jobs <- i }
    close(jobs)
    var total = 0
    for _ in 0..10 { total += (<-results) ?? 0 }
    print(total)
}
  • Green threads on every core.
  • Typed channels.
  • A tracing collector.
  • Hot reload on Linux.
  • HTTP, TLS, SQL and JSON.
  • VS Code, Zed and Neovim.
  • Linux and Windows.