Early development. Most of Python's syntax works—the parts that can be compiled statically—but there are gaps in the standard library and rough edges. See what works →

TurboPython is a general-purpose, statically typed language that compiles Python through C++ to a native binary. It combines Python's syntax with an ownership model that gives memory safety and predictable performance—no garbage collector, no automatic reference counting, no GIL.

  • 🐍Familiar Python syntax—standard Python with type annotations and a few extensions; most editors, type checkers and linters work out of the box.
  • Native compilation—compiles to a standalone native binary, with systems-level control: fixed-width integers, pointers and spans, and no GIL for multithreading.
  • Predictable performance and memory—no garbage collector and no automatic reference counting; memory is managed at compile time through ownership and borrowing, with opt-in Rc for shared ownership.
  • 🛡Compile-time memory safety—ownership rules catch use-after-free and aliasing bugs before the program runs.
  • 📦App or extension—ship a standalone binary, embed as a CPython extension, or call existing C/C++ through @native.
WIDTH, HEIGHT, MAX_ITER = 64, 28, 90
CHARS = " .:-=+*#%@"

def escape(cx: float, cy: float) -> int:
    x, y = 0.0, 0.0
    for i in range(MAX_ITER):
        if x*x + y*y > 4.0:
            return i
        x, y = x*x - y*y + cx, 2.0*x*y + cy
    return MAX_ITER

def main() -> None:
    for row in range(HEIGHT):
        cy = -1.2 + 2.4 * row / HEIGHT
        for col in range(WIDTH):
            cx = -2.4 + 3.2 * col / WIDTH
            idx = escape(cx, cy) % len(CHARS)
            print(CHARS[idx], end="")
        print("")

main()
$ tpy mandelbrot.py
Installation

Getting started

The entire TurboPython toolchain is an ordinary Python package, published on PyPI as tpy-lang; installing the package is the whole setup. Compilation uses a C++23 compiler on the PATH—GCC 13+ or Clang 19+. The tpy-lang[bundled] option comes with a compiler included.

1

Install the toolchain

$ pip install tpy-lang

Without a C++ compiler on the PATH, 'tpy-lang[bundled]' installs a self-contained compiler.

2

Run a program

$ tpy hello.py # compile and run
3

Or build a standalone binary

$ tpyc -b -O hello.py # optimized binary at __tpyc__/hello.d/release/hello

GCC 13+ ships with most current Linux distributions; on macOS, a recent Homebrew LLVM/Clang works. On Windows, the recommended setup is WSL.

How it differs from Python

Python syntax with static types and a strict ownership model.

Type annotations are required on function parameters, return types, and class fields; an unannotated integer literal defaults to a 32-bit Int32. Beyond these rules, most code reads like standard Python—minus Python's dynamic runtime features. Where a construct diverges from the static type and ownership model, the compiler reports it, and its diagnostics name the required change.

Ownership

Python reclaims objects automatically through reference counting, so a name is just a shared reference. TurboPython has no garbage collector: every value has exactly one owner — a function frame, a field, or a container — and the owner controls when it is freed. Locals and parameters still alias as they do in Python; persistent storage owns its values. Three cases follow, each shown with the compiler's actual output.

frame

A local is owned by the function that creates it.

Returning a local — won't compile
def make() -> Obj:
    o = Obj(1)
    return o   # freed when make returns
error:Cannot return local or temporary as reference. Object type 'Obj' is returned by reference. Use Own[Obj] to return by value.
Transfer ownership out — compiles
def make() -> Own[Obj]:
    o = Obj(1)
    return o   # ownership moves to the caller
ok:x = make() now owns the object.

o lives in make's frame and is freed when the function returns; returning it by reference would leave a dangling pointer. Own[Obj] hands the value itself to the caller, who becomes its owner.

field

A field owns its value.

Storing a borrowed value — compiles with a warning
class Cache:
    item: Tag
    def __init__(self, t: Tag) -> None:
        self.item = t   # Python would share a reference
warning:copies Tag into field; use copy() to make this explicit
Take ownership of the argument — compiles clean
class Cache:
    item: Tag
    def __init__(self, t: Own[Tag]) -> None:
        self.item = t   # the field takes ownership
ok:the argument moves into the field; no copy.

A field outlives the call that sets it, so it must own its value. Declaring the parameter Own[Tag] transfers the caller's object in; copy(t) is the alternative when the caller keeps its own. Fields are declared as typed class attributes and set in __init__ — there is no __dict__, so each one is a fixed, typed slot.

container

A collection owns its elements.

Keep using a value after storing it — compiles with a warning
l = []
o = Obj(1)
l.append(o)   # implicit copy: o is used below
o.x = 99
warning:copies Obj into owned storage; use copy() to make this explicit
Make the copy explicit — compiles clean
l = []
o = Obj(1)
l.append(copy(o))
o.x = 99      # the list holds its own copy
ok:the same copy happens either way; copy() just makes it explicit and clears the warning.

A list stores objects, not references, so the append copies either way — where Python would share the same object. copy() doesn't introduce the copy; it makes the unavoidable one explicit and clears the warning. If o weren't used afterward, it would be moved into the list instead — no copy at all.

Other differences

Ownership is the conceptual shift. The rest are smaller, mechanical rules — the type system's requirements, and the constraints a compiled language places on Python's more dynamic features. They become familiar within the first few files.

🏷

Annotations required

Function parameters, returns, and class fields are annotated; local variables are inferred. Python's optional hints become the contract.

🔢

Int32 by default

A bare integer is a 32-bit Int32. Int64/UInt32/... pick a width; int is arbitrary-precision (heap-allocated for large values).

📦

Value vs reference

Primitives, tuples and views copy; classes, list, dict, set are reference types, passed and returned by reference.

🔤

str adapts to context

A str is an owned string or a borrowed view depending on context; String and StrView spell it out. Plain str is the right default when allocations are acceptable.

🔒

Static, not dynamic

No eval, monkey-patching, or runtime __dict__ mutation. Types and structure are fixed at compile time.

🧰

Core types in tpy

TurboPython's own types (Own, Int32, Span, ...) live in the tpy module — a file can pull them all in with from tpy import *.

🤖 AI assistants are supported. tpy --install-agent-docs docs writes a TurboPython primer alongside the code — the Python-to-TPy delta, the ownership rules, and the idioms an assistant needs to generate correct TurboPython.