Skip to content
kacyPublic

About

A small language that compiles itself to native code. Python shaped but with the speed and confidence of Go and Rust

Topics

Resources

Contributing

Security policy

Stars

4 stars

Watchers

0 watching

Forks

Repository files navigation

pith

ci license: mit

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).

the language in one file

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.

why you might want it

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.

the numbers

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.

what it does not do

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 weak edge leaks. an opt-in trial-deletion collector exists behind PITH_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, with lock and install but nothing that fetches.
  • no debugger, and a runtime error prints its message without a stack trace.
  • .value() on a none stops the program with a runtime error; use if let, match or unwrap_or when 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.

install

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

build from source

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.

the standard library

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.

proof it is usable

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."

cli

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

building and testing

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

project layout

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.

syntax highlighting on github

.pith files map to python highlighting via .gitattributes as a stopgap. the textmate grammar and the linguist submission checklist live in tooling/highlighting/.

license

mit

About

A small language that compiles itself to native code. Python shaped but with the speed and confidence of Go and Rust

Topics

Resources

Contributing

Security policy

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages