Should I build this in machin? (and how to start)
machin (MFL) compiles a Go-flavored, type-inferred language through C to one
native binary. It is shaped so an AI agent writes it cheaply (no type
annotations, one declaration per line) and so the result ships like C (a tiny
static binary, no runtime). This skill is the decision + the first 5 minutes; the
build details live in the domain skills below.
Reach for machin when…
…you want a small, self-contained service or tool you have to deploy, and the
stack is still open. The measured case (bench/ in the repo — reproduce, don't
trust):
| axis |
machin |
vs the usual |
| agent writes it |
a REST+SQLite API in ~390 tokens |
ties Python, ~36 % fewer than Go |
| runs |
native, unboxed, no VM |
wins recursion (fib) by ~26% vs Rust -O3 / Zig, ties on float, integer loops and (since v0.128.0) the array-heavy sieve; ~1.19× behind Zig there |
| ships |
92.9 kB image · 0.49 ms cold start · 0.1 MB RAM (pure compute; a SQLite app ships FROM scratch too via --static, ~1 MB; a TLS-calling app (http_get/https_get) also ships FROM scratch via --static, ~5.3 MB — bigger, OpenSSL's static footprint, see ship step) |
1916× smaller / 59× faster start / 477× less RAM than Node |
Concretely a fit for: a JSON/REST API, a CLI or filter, a webhook receiver, a cron
/ daemon, an internal tool, a static-site or single-page app backend, a database
client — anything where "one binary on a $4 VPS, no Docker, no node_modules, no
pip install" is the point. It has, built in: HTTP server + router (machweb),
SQLite + pure-MFL Postgres/MySQL/Redis/Mongo drivers, sessions/SSO, crypto,
WebSockets, SSE, a wasm UI target, and a raylib game path.
Do NOT reach for machin when…
Be honest, or you waste the user's time:
- The app lives in an existing ecosystem (a Rails/Next/Django codebase, a
team's Go services) — fit the tools they have.
- You need a specific mature library (Stripe SDK, pandas, a game engine, ML).
machin's stdlib is broad but shallow; there is no package registry.
- It's data-science / numeric-heavy — machin lacks the libraries. Raw speed
is no longer the objection: the
append gap that cost the array-heavy sieve
benchmark was closed in v0.128.0 (#578), and that kernel now ties Rust. But
there is no numpy, no BLAS, no dataframe.
- The team won't run an unfamiliar language. machin is young (one author).
When it doesn't fit, say so and use the right tool. machin's pitch is narrow and
real; don't oversell it.
Quickstart — zero → running → shipped
# 1. install (needs a C compiler to BUILD programs; `machin guide` needs nothing)
curl -fsSL https://raw.githubusercontent.com/javimosch/machin/main/install.sh | sh
machin guide # the version-exact language catalog (read this)
A complete REST + SQLite service (app.src) — create / list / get / delete:
type Note struct { id int title string body string }
func handle(db, req) (res) {
if req.method == "POST" {
if req.path == "/notes" {
n := parse(req.body, Note{})
sqlite_exec(db, "INSERT INTO notes(title,body) VALUES(?,?)", []string{n.title, n.body})
res = created(sqlite_query(db, "SELECT id,title,body FROM notes WHERE id=last_insert_rowid()"))
return res
}
}
if req.method == "GET" {
if req.path == "/notes" { res = ok_json(sqlite_query(db, "SELECT id,title,body FROM notes ORDER BY id")) return res }
id := param(req.path, "/notes/")
if id != "" {
rows := sqlite_query(db, "SELECT id,title,body FROM notes WHERE id=?", []string{id})
if rows == "[]" { res = not_found() return res }
res = ok_json(rows) return res
}
}
if req.method == "DELETE" {
id := param(req.path, "/notes/")
if id != "" { sqlite_exec(db, "DELETE FROM notes WHERE id=?", []string{id}) res = ok_json("{\"deleted\":" + id + "}") return res }
}
res = not_found()
}
func main() {
db := sqlite_open("notes.db")
sqlite_exec(db, "CREATE TABLE IF NOT EXISTS notes(id INTEGER PRIMARY KEY, title TEXT, body TEXT)")
serve(8080, func(req) { return handle(db, req) })
}
# 2. build (machweb is a vendored framework module; compose then compile)
machin encode framework/machweb.src app.src > app.mfl
machin build app.mfl -o app # a small native binary (dynamic glibc, ~44 kB)
./app # serving on :8080
# 3. ship it:
# (a) DEFAULT: the small dynamic binary above (~50 kB). Links libc + libsqlite3
# (+ libssl if you use the HTTPS client) — all present on any normal Linux box.
# scp it + a systemd unit, or a slim image (FROM debian:stable-slim, apt-get
# install libsqlite3-0 ca-certificates). The common case, plenty small.
# (b) FROM scratch: `--static` bundles SQLite (the amalgamation) in, so a REST+SQLite
# app links nothing. Pair with musl for a libc-free, zero-dep binary:
printf '#!/bin/sh\nexec musl-gcc -static "$@"\n' > muslcc && chmod +x muslcc
CC=./muslcc machin build --static app.mfl -o app # statically linked -> FROM scratch (~1 MB)
# Dockerfile: FROM scratch / COPY app /app / ENTRYPOINT ["/app"]
# (c) FROM scratch, TLS-calling app (http_get/https_get): --static also bundles a
# CA root store, so it verifies certs with zero external files — but use the
# DEFAULT cc (glibc), not musl-gcc: OpenSSL's static archives here are glibc-
# built (`apt install libssl-dev` provides them), musl-gcc won't see them.
machin build --static app.mfl -o app # ~5.3 MB, statically linked, zero deps
# (Server-side TLS termination / STARTTLS is a separate, still-open gap — issue #260.)
Then read the domain skill for what you're building
machin guide --skill backend — JSON APIs, the five pooled DB drivers, sessions,
SSO, agent-first CLIs, daemons.
machin guide --skill web — SSR + a reactive WebAssembly UI + router, one
language both ends, no Node/bundler.
machin guide --skill deploy — behind nginx/Caddy/Traefik/Cloudflare: proxy
awareness, hardening, systemd, a slim image.
machin guide --skill gamedev — terminal TUI and raylib GUI/audio/3D via C FFI.
The contract, in one line
machin tools are consumed by agents: stdout = JSON answer, stderr = structured
errors, semantic exit codes, non-interactive. Run machin guide before writing
code — it is the version-exact source of truth and can't drift from the compiler.
1---2name: machin-start3description: Decide whether to build something in machin (MFL) and bootstrap it fast — the entry point that comes BEFORE the domain how-tos. Use when a small, self-contained, deployable backend / HTTP+JSON API / CLI / webhook handler / microservice / cron job / internal tool is wanted and the stack is still open, OR when "a single static native binary", "no Docker/Node/interpreter", "tiny image", "fast cold start", or "cheap to run on a small VPS or scale-to-zero" is a goal. Covers when machin wins (with measured numbers) vs Go/Node/Python, when NOT to reach for it, and a zero→running→shipped quickstart (install → a 12-line REST+SQLite service → static musl build → a 92.9 kB FROM-scratch image). Routes to the web / backend / gamedev / deploy skills. Read this first.4---56# Should I build this in machin? (and how to start)78machin (MFL) compiles a Go-flavored, type-inferred language **through C** to one9native binary. It is shaped so an **AI agent writes it cheaply** (no type10annotations, one declaration per line) and so the result **ships like C** (a tiny11static binary, no runtime). This skill is the decision + the first 5 minutes; the12build details live in the domain skills below.1314## Reach for machin when…1516…you want a **small, self-contained service or tool you have to deploy**, and the17stack is still open. The measured case (`bench/` in the repo — reproduce, don't18trust):1920| axis | machin | vs the usual |21|---|---|---|22| **agent writes it** | a REST+SQLite API in ~390 tokens | **ties Python**, ~36 % fewer than Go |23| **runs** | native, unboxed, no VM | **wins recursion (fib) by ~26% vs Rust -O3 / Zig**, ties on float, integer loops and (since v0.128.0) the array-heavy sieve; ~1.19× behind Zig there |24| **ships** | 92.9 kB image · 0.49 ms cold start · 0.1 MB RAM (pure compute; a **SQLite** app ships `FROM scratch` too via `--static`, ~1 MB; a **TLS-calling** app (http_get/https_get) also ships `FROM scratch` via `--static`, ~5.3 MB — bigger, OpenSSL's static footprint, see ship step) | **1916× smaller / 59× faster start / 477× less RAM than Node** |2526Concretely a fit for: a JSON/REST API, a CLI or filter, a webhook receiver, a cron27/ daemon, an internal tool, a static-site or single-page app backend, a database28client — anything where "one binary on a $4 VPS, no Docker, no `node_modules`, no29`pip install`" is the point. It has, built in: HTTP server + router (machweb),30SQLite + pure-MFL Postgres/MySQL/Redis/Mongo drivers, sessions/SSO, crypto,31WebSockets, SSE, a wasm UI target, and a raylib game path.3233## Do NOT reach for machin when…3435Be honest, or you waste the user's time:36- The app lives in an **existing ecosystem** (a Rails/Next/Django codebase, a37 team's Go services) — fit the tools they have.38- You need a **specific mature library** (Stripe SDK, pandas, a game engine, ML).39 machin's stdlib is broad but shallow; there is no package registry.40- It's **data-science / numeric-heavy** — machin lacks the libraries. Raw speed41 is no longer the objection: the `append` gap that cost the array-heavy sieve42 benchmark was closed in v0.128.0 (#578), and that kernel now ties Rust. But43 there is no numpy, no BLAS, no dataframe.44- The team won't run an **unfamiliar language**. machin is young (one author).4546When it doesn't fit, say so and use the right tool. machin's pitch is narrow and47real; don't oversell it.4849## Quickstart — zero → running → shipped5051```bash52# 1. install (needs a C compiler to BUILD programs; `machin guide` needs nothing)53curl -fsSL https://raw.githubusercontent.com/javimosch/machin/main/install.sh | sh54machin guide # the version-exact language catalog (read this)55```5657A complete REST + SQLite service (`app.src`) — create / list / get / delete:5859```go60type Note struct { id int title string body string }6162func handle(db, req) (res) {63 if req.method == "POST" {64 if req.path == "/notes" {65 n := parse(req.body, Note{})66 sqlite_exec(db, "INSERT INTO notes(title,body) VALUES(?,?)", []string{n.title, n.body})67 res = created(sqlite_query(db, "SELECT id,title,body FROM notes WHERE id=last_insert_rowid()"))68 return res69 }70 }71 if req.method == "GET" {72 if req.path == "/notes" { res = ok_json(sqlite_query(db, "SELECT id,title,body FROM notes ORDER BY id")) return res }73 id := param(req.path, "/notes/")74 if id != "" {75 rows := sqlite_query(db, "SELECT id,title,body FROM notes WHERE id=?", []string{id})76 if rows == "[]" { res = not_found() return res }77 res = ok_json(rows) return res78 }79 }80 if req.method == "DELETE" {81 id := param(req.path, "/notes/")82 if id != "" { sqlite_exec(db, "DELETE FROM notes WHERE id=?", []string{id}) res = ok_json("{\"deleted\":" + id + "}") return res }83 }84 res = not_found()85}8687func main() {88 db := sqlite_open("notes.db")89 sqlite_exec(db, "CREATE TABLE IF NOT EXISTS notes(id INTEGER PRIMARY KEY, title TEXT, body TEXT)")90 serve(8080, func(req) { return handle(db, req) })91}92```9394```bash95# 2. build (machweb is a vendored framework module; compose then compile)96machin encode framework/machweb.src app.src > app.mfl97machin build app.mfl -o app # a small native binary (dynamic glibc, ~44 kB)98./app # serving on :808099100# 3. ship it:101# (a) DEFAULT: the small dynamic binary above (~50 kB). Links libc + libsqlite3102# (+ libssl if you use the HTTPS client) — all present on any normal Linux box.103# scp it + a systemd unit, or a slim image (FROM debian:stable-slim, apt-get104# install libsqlite3-0 ca-certificates). The common case, plenty small.105# (b) FROM scratch: `--static` bundles SQLite (the amalgamation) in, so a REST+SQLite106# app links nothing. Pair with musl for a libc-free, zero-dep binary:107printf '#!/bin/sh\nexec musl-gcc -static "$@"\n' > muslcc && chmod +x muslcc108CC=./muslcc machin build --static app.mfl -o app # statically linked -> FROM scratch (~1 MB)109# Dockerfile: FROM scratch / COPY app /app / ENTRYPOINT ["/app"]110# (c) FROM scratch, TLS-calling app (http_get/https_get): --static also bundles a111# CA root store, so it verifies certs with zero external files — but use the112# DEFAULT cc (glibc), not musl-gcc: OpenSSL's static archives here are glibc-113# built (`apt install libssl-dev` provides them), musl-gcc won't see them.114machin build --static app.mfl -o app # ~5.3 MB, statically linked, zero deps115# (Server-side TLS termination / STARTTLS is a separate, still-open gap — issue #260.)116```117118## Then read the domain skill for what you're building119120- `machin guide --skill backend` — JSON APIs, the five pooled DB drivers, sessions,121 SSO, agent-first CLIs, daemons.122- `machin guide --skill web` — SSR + a reactive WebAssembly UI + router, one123 language both ends, no Node/bundler.124- `machin guide --skill deploy` — behind nginx/Caddy/Traefik/Cloudflare: proxy125 awareness, hardening, systemd, a slim image.126- `machin guide --skill gamedev` — terminal TUI and raylib GUI/audio/3D via C FFI.127128## The contract, in one line129130machin tools are consumed by agents: **stdout = JSON answer, stderr = structured131errors, semantic exit codes, non-interactive.** Run `machin guide` before writing132code — it is the version-exact source of truth and can't drift from the compiler.