Skip to main content

Tantu logo

Tantu

CI

A statically-typed, Python-readable language with fibers and channels — compiled to bytecode and run on a hand-built stack VM, implemented in Python 3.12 with zero third-party runtime dependencies.

Try it without installing anything: tantu-play.netlify.app has the full guide and an in-browser playground running this interpreter.

  • Design contract: DESIGN.md (v1.2) is the single source of truth.
  • Decisions log: DECISIONS.md records anything the design leaves silent.
  • Status: M1–M9 complete — a full pipeline (lexer → parser → checker → compiler → linker → stack VM), static types with no null (Option/Result), pattern matching, closures, fibers + channels with select, multi-file modules, and user-defined generics. The full test suite and all examples run in CI.

See CHANGELOG.md for release notes and CONTRIBUTING.md if you want to hack on it.

Why Tantu

Every language I like makes me give something up. Python reads like pseudocode but catches nothing before it runs and has no concurrency primitives of its own. Go gives you cheap goroutines, then shares memory by default and only asks you not to race — the detector is opt-in and finds races at runtime, if you're lucky. Rust proves your concurrency is safe but charges you the borrow checker for the proof.

Tantu is a small answer to a narrow question: can a language read like Python, catch your mistakes before it runs, and make data races impossible without a borrow checker?

It gets there with three constraints:

  • No null. Absence and failure are ordinary values (Option, Result), and the checker won't let you ignore them. match and the ? operator are the only ways through.
  • Immutable by default. let bindings and every collection are values; push returns a new list. var buys you a reassignable binding and nothing more.
  • No shared mutable state across fibers. Capturing a var in a closure or a spawned fiber is a compile error. Fibers talk over typed channels, never shared memory.

That third rule is why Tantu exists. A Tantu program can't contain a data race — not because you were careful, but because the language took away the tools to write one. You get Go's concurrency ergonomics (spawn, chan, select) with a guarantee Go doesn't give you, for the price of one checker rule instead of an ownership system.

When to use it, and when not to

Use Tantu if you want a tiny statically-typed language with real concurrency that you can read end to end — for scripts, for exercises, or to see how these pieces fit together. The whole thing is pure Python with no runtime dependencies, and each stage (lexer, parser, checker, compiler, stack VM) is a small module you can read on its own. It's as much something to study as something to run.

Don't use it if you need speed or a library ecosystem. It runs on a bytecode VM written in Python, with a cooperative, deterministic scheduler; it's built for correctness and clarity, not throughput. v1 is deliberately small: erased generics, one concurrency model, one way to handle errors. Need performance? Use Go or Rust. Need libraries? Use Python. Tantu is for when you'd rather have a language you can hold in your head, with the safety properties above spelled out.

A taste

enum Shape:
    Circle(Float)
    Rect(Float, Float)

fn area(s: Shape) -> Float:
    match s:
        Circle(r) => 3.14159 * r * r
        Rect(w, h) => w * h

fn main() -> Unit:
    print(area(Circle(2.0)))
    print(area(Rect(3.0, 4.0)))

No null: absence and failure are values (Option[T], Result[T, E]) unwrapped by match or the ? operator. Bindings are immutable by default (let; var opts in). Concurrency is CSP — lightweight spawned fibers passing messages over typed chan[T] channels, never shared mutable state. See examples/ for runnable programs (hello, arithmetic, closures, collections, options, fib, fizzbuzz, word-frequency, channels, shapes, select, generics).

select waits on several channel operations at once and takes the first ready arm (a recv arm binds Option[T]; a closed channel makes its recv arm fire with None). With no ready arm it blocks until one is; an else arm makes it non-blocking. The canonical use is a timeout — race real work against a fiber that sleeps then signals:

select:
    v = recv(result) =>
        match v:
            Some(n) => print(n)
            None => print("closed")
    deadline = recv(timeout) =>
        print("timed out")

Ready arms are polled in source order, so scheduling stays deterministic and testable.

Install

Tantu needs Python 3.12 or newer and installs a tantu command.

pip install tantu     # from PyPI (or: pipx install tantu)
pip install .         # from a clone

Usage

tantu run file.tn     # run a program (use `run -` to read stdin)
tantu check file.tn   # static check only
tantu dis file.tn     # disassemble to bytecode
tantu                   # REPL
tantu --version

If you're working from a checkout without installing, python -m tantu ... takes the same arguments.

Modules

A program is a set of sibling *.tn files in one directory. import name makes the members of name.tn available as name.member:

# mathlib.tn
fn square(n: Int) -> Int:
    n * n
let answer = 42

# main.tn
import mathlib
fn main() -> Unit:
    print(mathlib.square(5))   # 25
    print(mathlib.answer)      # 42

Running tantu run main.tn discovers the import graph from the root, checks each module in topological order, links every module's globals into one flat table, runs each module's top-level initializers (dependencies first), then calls the root's main(). Import cycles and missing modules are compile-time errors. (import needs a source file on disk — it isn't available for stdin or the REPL.)

Generics

Functions, enums, and structs can take type parameters in [ ]. Generics are fully parametric (no bounds) and erased — a generic definition compiles to a single function or descriptor, and type arguments are always inferred from the call, never written explicitly:

fn map[T, U](xs: List[T], f: (T) -> U) -> List[U]:
    var out: List[U] = []
    for x in xs:
        out = push(out, f(x))
    out

enum Tree[T]:
    Leaf
    Node(Tree[T], T, Tree[T])

struct Pair[A, B]:
    first: A
    second: B

Inside a generic body a type parameter is opaque: it can be passed, stored, matched, and sent over a channel, but not added, compared, or called — there is nothing the checker knows it supports. A generic function is call-only (not a first-class value); pass a lambda with concrete types if you need one as a value.

Development

python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"
pytest

Metadata

Release files for tantu 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tantu 0.1.0
File Size Uploaded
tantu-0.1.0.tar.gz 278.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for tantu 0.1.0
File Interpreter ABI Platform
tantu-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 352.8 kB

Release files / tantu-0.1.0.tar.gz

Download URL tantu-0.1.0.tar.gz
Size 278.3 kB
Tags Source
SHA-256 checksum
How to use checksums
d8a26d720dbb51a7acf1ef50631a35aa669f6dfffeda5f4883d73fdb00c5f545
BLAKE2b-256 checksum
How to use checksums
a709b9bcb14c4cc425b36a82de42c2c41633d295434875a6d6644379c1d9c673
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 12, 2026.

Transparency log

Release files / tantu-0.1.0-py3-none-any.whl

Download URL tantu-0.1.0-py3-none-any.whl
Size 74.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ebb570afdeccd11adafb2bc1448d8a0ff9d0867e99e3c0dcc5c159f3a7c7de63
BLAKE2b-256 checksum
How to use checksums
06aab95e108d5dff53623a7d70a7e521258b9c313739aff6bdbfd321878d2484
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 12, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page