mach

A systems programming language with no hidden behavior.

Statically typed. Explicitly designed. No magic.

Installs the latest release to ~/.local/bin.

On Linux:

curl -fsSL https://machlang.org/install.sh | sh

On Windows, run it in PowerShell:

irm https://machlang.org/install.ps1 | iex

Run it in PowerShell.

macOS support is planned.

main.mach
use        std.runtime;
use print: std.print;

fun fib(n: i64) i64 {
    if (n < 2) { ret n; }
    ret fib(n - 1) + fib(n - 2);
}

$main.symbol = "main";
fun main(argc: i64, argv: **u8) i64 {
    print.printf("hello, mach\n");
    print.printf("fib(10) = %d\n", fib(10));
    ret 0;
}

Why mach

Mach is small and explicit: what you read is what executes, and every cost is visible in the code that incurs it. The whole toolchain is a single binary — it builds, links without an external linker, tests, vendors dependencies, and cross-compiles. Nothing is hidden to learn, and nothing has to be wired up.

Built for compilers, runtimes, operating systems, tooling — anywhere performance is a requirement and hidden behavior is a liability.

hello.mach
use          std.runtime;
use print:   std.print;

$main.symbol = "main";
fun main(argc: i64, argv: **u8) i64 {
    print.println("Hello, World!");
    ret 0;
}
No hidden runtime

The entry point is ordinary code: you name the symbol and import the runtime yourself. What you read is what executes — nothing runs that you didn't write.

alloc.mach
use alloc:  std.allocator;
use page:   std.allocator.page;
use result: std.types.result;
use std.types.result.Result;
use std.types.string.str;

# create a page-backed allocator, then pass it by hand
var a: alloc.Allocator;
page.make(?a);

# anything that allocates takes one; anything that doesn't never will
val r:      Result[*bool, str] = alloc.allocate[bool](?a, count);
val primes: *bool              = result.unwrap_ok[*bool, str](r);

# the same allocator frees exactly what it allocated
alloc.deallocate[bool](?a, primes, count);
Manual memory

No garbage collector, no hidden allocation. Memory flows through allocators you create and pass explicitly — and the same one frees what it allocated.

parse.mach
use parse:  std.text.parse;
use print:  std.print;
use result: std.types.result;
use std.types.result.Result;
use std.types.string.str;

# a fallible call returns a value, not a thrown exception
val r: Result[u64, str] = parse.parse_u64(argv[1], 10);

# the error path is visible — you check it before you unwrap
if (result.is_err[u64, str](r)) {
    print.printf("error: %s\n", result.unwrap_err[u64, str](r));
    ret 1;
}

val limit: u64 = result.unwrap_ok[u64, str](r);
Errors are values

A fallible call returns a Result you read like any other value. There are no exceptions and no hidden unwinding — you check the error before you reach the result.

Get Started

From nothing to a running binary — one toolchain, no external linker, no build system to configure.

~
$ curl -fsSL https://machlang.org/install.sh | sh
# verifies the download, installs to ~/.local/bin
$ mach init my-app
$ cd my-app
$ mach dep pull
# vendors dependencies into dep/, pinned in mach.lock
$ mach build .
# one binary builds and links — no external linker
$ mach run .
Hello, World!

Next: language reference — a pamphlet, not a bible  ·  mach-sieve — a standalone starting point

Join the Discord