a small language for writing servers. python-shaped syntax, native binaries, result types instead of exceptions, reference counting instead of a garbage collector. the compiler is written in pith, and so is the standard library: the tls 1.2 and 1.3 protocol, the http/2 client and server, the postgres and mysql wire protocols, the regex engine, gzip. when something goes wrong in the stack, you can read the code that did it.
import std.web as web
import std.net.http as http
fn greet(req: web.Request) -> http.HttpResponse:
return http.text(200, "hello, " + req.param("name"))
fn main() -> Int!:
mut app := web.new()
app = app.get("/", fn(req: web.Request) => http.text(200, "welcome"))
app = app.get("/hello/:name", greet)
return app.listen("0.0.0.0", 8080)!
that is a complete http server, and it comes with more than it shows.
every route is already wrapped in a trace span and red metrics; an
inbound traceparent joins the caller's trace, GET /metrics serves
prometheus text, and one obs.init() call ships spans and metrics to
an opentelemetry collector. listen_tls negotiates http/2 or http/1.1
over tls 1.2 and 1.3, one shutdown.on_signals() call makes a sigterm
drain in-flight requests before the process exits, and rate limiting,
circuit breaking, sessions, csrf and
cors are middleware you add one line at a time. no dependencies, one
static binary, and all of it is standard library you can read
(docs/web.md, docs/telemetry.md).
enum Shape:
Circle(Float)
Rect(Float, Float)
fn area(s: Shape) -> Float:
match s:
Shape.Circle(r) => 3.14159 * r * r
Shape.Rect(w, h) => w * h
fn parse_radius(text: String) -> Float!:
n := parse_int(text)!
if n <= 0:
fail "radius must be positive"
return n.to_float()
fn main() -> Int!:
shapes := [Shape.Circle(2.0), Shape.Rect(3.0, 4.0)]
mut total := 0.0
for s in shapes:
total = total + area(s)
print("total area: {total:.2}")
r := parse_radius("3")!
print("one more: {area(Shape.Circle(r)):.2}")
fallback := parse_radius("") catch 1.0
print("fallback radius: {fallback}")
return 0
enums carry payloads and match takes them apart. errors are values:
T! is a result, fail makes one, ! passes it up, catch supplies
a fallback. a function that cannot fail cannot pretend otherwise.
interpolation takes format specs. there is no null and there are no
exceptions. an out-of-range index or a missing map key propagates as
an error inside a T! function and stops the process outside one.
the whole stack is readable. 126 standard library modules, 42,000
lines of pith: the tls implementation, http/1.1 and http/2,
websockets, grpc, the database wire protocols, json, toml, yaml,
compression, hashing, a linear-time regex engine. when the language
could not express something well, that became a language feature to
build rather than a foreign library to hide behind. the exceptions
live in the rust runtime: the cryptographic primitives (aead ciphers,
key exchange, signatures) come from the ring crate, zstd links the
reference libzstd, and a few hot loops such as the sha block functions
have native versions.
it compiles itself, and ci proves it. the lexer, parser, checker, formatter, linter, language server and ir emitter are pith. the build reaches a fixed point: the compiler compiles a compiler that compiles an identical compiler, verified on every merge.
errors are data. T!E carries a typed payload when callers need
to inspect a failure, and defer / errdefer run cleanup on every
exit or only the failing one. see docs/errors.md and
docs/defer.md.
memory is managed at compile time. the compiler emits retain and
release pairs under a borrowed-by-default discipline
(docs/ownership.md), so there are no gc pauses and
no runtime to tune. a struct graph breaks its own cycles with a weak
field.
concurrency is structured, and green by default. spawn and
await with Task[T], channels, select, mutexes, wait groups,
contexts and timers, on an m:n green runtime with an epoll reactor
(docs/concurrency.md). the os-thread backend is
one environment variable away.
the tooling talks json. check, lint and doc all take
--json, every diagnostic has a stable code, fmt has one canonical
style, and pith lsp speaks the language server protocol to neovim or
vs code (docs/editors.md).
builds are fast. on the benchmark machine below, hello world compiles in about 40 ms, the http benchmark server in about 1.6 seconds, and the entire self-hosted compiler in about 1.3.
measured 2026-11-05 on a ryzen 9 9950x, every language pinned to the same eight cores, against go 1.27 and rust 1.99 ports that do the same work and print the same checksums. the full tables, one row per benchmark, are in bench/README.md; the history and the profiling behind them are in docs/performance.md.
| workload | pith | go | rust |
|---|---|---|---|
| json decode, 20M small objects | 716 ms | 3562 ms | 1105 ms |
| substring search | 45 ms | 221 ms | 223 ms |
| channel fan-out, 1M messages | 40 ms | 49 ms | 471 ms |
| event ledger, 200k events (json, collections, crypto) | 72 ms | 95 ms | 50 ms |
| sort 100k ints, 30 rounds | 313 ms | 511 ms | 60 ms |
| csv encode | 416 ms | 309 ms | 130 ms |
| error handling through closures | 359 ms | 182 ms | 160 ms |
| sha256, 256 MB | 109 ms | 96 ms | 96 ms |
| http server at 256 connections, req/s | 1.92M | 2.27M (minimal), 992k (net/http) | 1.75M (hyper) |
| api gateway, plain forwarding at 64 / 1024 connections, req/s | 472k / 467k | 280k / 350k (net/http) | no port |
- pith is faster than go on json decoding, text search, the event ledger and catalog workloads, int sorting, channels, spawning and parallel compute. it is faster than rust on half the json shapes, text search, channels, task churn and grpc calls. its http server handles more requests per second than hyper up to 256 connections, and has the lowest p99 latency of every go and rust server measured at every connection count.
examples/gateway, a reverse proxy with auth, retries and a circuit breaker, forwards 1.2 to 1.7x as many requests per second as the same gateway in go's net/http, with a p99 3.4 to 4.4x lower and 40 to 70% less memory. with es256 tokens to check, the two are level at 256 and 1024 connections.- go is ahead on closures, reference cycles, task scheduling, string building, string-keyed maps, concurrent grpc calls and raw http throughput against a minimal server.
- rust is ahead on most single-threaded compute, mostly by 1.4x to 5.9x. the outlier is walking decoded json (9.0x).
these come from one machine, with clients and servers on the same box over loopback. the gateway has no rust port, and a day earlier go was 2.4x ahead of it. part of the change is a fix for a scheduler bug that starved new connections, and the change behind most of it makes a mutex wait longer when cpu-bound tasks share its worker. examples/gateway/README.md has the details.
the current list lives in docs/limitations.md, kept current with the compiler and carrying issue numbers for the open defects. the ones to know before you start:
- a strong reference cycle with no
weakedge leaks. an opt-in trial-deletion collector exists behindPITH_CYCLE_GC, off by default. - tls 1.2 is a fallback: ecdhe with aead suites only, and no session
resumption. client certificates work on both versions. 1.3 is the full
implementation, and
require_tls13()refuses the fallback. - no package registry. dependencies are local paths in
pith.toml, withlockandinstallbut nothing that fetches. - no debugger, and a runtime error prints its message without a stack trace.
.value()on anonestops the program with a runtime error; useif let,matchorunwrap_orwhen the optional can be empty.- gzip compresses with fixed huffman only, and the regex engine skips counted repeats, backreferences, lazy quantifiers and lookaround on purpose.
curl -fsSL https://pithlang.com/install.sh | sh
the script installs the toolchain in ~/.pith and prints the line to add
to your PATH. set PITH_HOME to install somewhere else, or
PITH_VERSION to pin a release such as v0.2.12. prebuilt toolchains
cover linux and macos on x86_64 and arm64. pith links each program with
the system c compiler, so you also need cc: the xcode command line tools
on macos, gcc or clang on linux.
printf 'fn main():\n print("hello")\n' > hello.pith
pith run hello.pith
you need rust and cargo for the native backend.
cargo build --release
./target/release/pith run examples/hello.pith
./target/release/pith new myapp
pith new scaffolds a library package with a pith.toml, a Makefile,
a test and an AGENTS.md. to build the compiler with itself:
make self-host
./self-host/pith_main check examples/hello.pith
the first build compiles the ir driver from a tracked seed at
self-host/bootstrap/ir_driver.ir, which is what breaks the circular
dependency of a compiler written in its own language. after that
everything rebuilds from source.
126 modules, all pith. the areas, with the doc that goes deepest:
| area | modules | read |
|---|---|---|
| networking | tcp, dns, url, http, http2, websocket, tls, sse, grpc | tls, grpc, web |
| databases | sql, postgres, mysql, redis, and db on top |
db |
| web apps | web: routing, middleware, sessions, csrf, cors | http apps |
| data | json, toml, yaml, csv, config, table | yaml |
| exact numbers | bigint, decimal | numbers |
| bytes and crypto | hash, checksum, encoding, crypto, bits, binary | auth |
| compression | gzip, zlib, zstd, tar, zip | |
| text | regex, scanner, fmt, html, template, text, strings | unicode, html |
| i18n | locales, message catalogs, cldr plural rules | i18n |
| app plumbing | log, metrics, cli, env, testing, time, datetime, rand, uuid, math | logging |
| resilience | retry, rate limiting, circuit breaking | resilience |
| observability | trace, prometheus, otlp, obs | telemetry |
| terminals | term: raw mode, input, styling, widgets, an elm-style runtime | tui |
| concurrency | concurrent: contexts, groups, timers; iter for lazy pipelines | concurrency |
the stdlib reference site is generated from the source comments, so what you read there is what the compiler read.
real programs live in tools/ and run against golden output in ci: a
static site generator, a web log analyzer, a json api client talking
to a pith http server, a worker pool over spawn and channels, the
protobuf code generator, and the documentation site generator that
builds the stdlib reference. they are the answer to "what does
non-trivial pith look like."
| command | what it does |
|---|---|
pith run <file> |
compile and run |
pith build <file> |
compile to a native binary |
pith test <file> |
run test blocks; --filter <substr> and --tag <name> select, --json reports records |
pith check <file> |
type check; --json for machine output |
pith fmt <file> |
format; --check to verify |
pith lint <file> |
conventions; --json available |
pith doc <file> |
docs; --check requires every pub item documented |
pith lsp |
language server over stdio |
pith new <dir> |
scaffold a package |
pith package <cmd> |
check, test, lint, doc, deps, lock, install, inspect, context |
local packages resolve through pith.toml:
[dependencies]
greeter = { path = "../greeter" }from greeter import greet
make build # native backend
make self-host # the compiler, compiled by itself
make test # the full suite
make run-examples # 131 example programs against snapshots
make status-audit # current corpus and size numbers
self-host/ the compiler, in pith: lexer, parser, checker,
formatter, linter, docgen, language server, ir emitter
cranelift/ the native backend, in rust: ir consumer, codegen,
runtime (arc, collections, net)
std/ the standard library
examples/ 141 runnable programs, 134 with expected output
tests/ regression, invalid-program, leak, and golden fixtures
tools/ real programs in pith: codegen, generators, fuzzers,
a log analyzer, a worker pool
docs/ architecture, ownership, errors, grammar, limitations
make status-audit prints line counts, with comments and blank lines
excluded. start with docs/architecture.md to
change the compiler, docs/contributing.md for
the development loop, and docs/testing.md for how
tests are written and run.
.pith files map to python highlighting via .gitattributes as a
stopgap. the textmate grammar and the linguist submission checklist
live in tooling/highlighting/.
mit