Rc for shared ownership.@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()
The entire TurboPython toolchain is an ordinary Python package for Python 3.12 or newer,
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.
Without a C++ compiler on the PATH, 'tpy-lang[bundled]' installs a self-contained compiler.
GCC 13+ ships with most current Linux distributions; on macOS, a recent Homebrew LLVM/Clang works. On Windows, the recommended setup is WSL.
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.
Packages written for CPython, such as numpy or requests, cannot be
imported. Everything a program imports is compiled together with it. A dependency has to be written in
TurboPython or ported to it. What ships today is a subset of the standard library and TurboPython's own
tplib libraries; Compatibility lists them.
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.
def make() -> Obj: o = Obj(1) return o # freed when make returns
def make() -> Own[Obj]: o = Obj(1) return o # ownership moves to the caller
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.
class Cache: item: Tag def __init__(self, t: Tag) -> None: self.item = t # Python would share a reference
class Cache: item: Tag def __init__(self, t: Own[Tag]) -> None: self.item = t # the field takes ownership
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.
l: list[Obj] = [] o = Obj(1) l.append(o) # implicit copy: o is used below o.x = 99
l: list[Obj] = [] o = Obj(1) l.append(copy(o)) o.x = 99 # the list holds its own copy
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.
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.
Function parameters, returns, and class fields are annotated; local variables are inferred. Python's optional hints become the contract.
A bare integer is a 32-bit int32. int64/uint32/... pick a width; int is arbitrary-precision (heap-allocated for large values).
Primitives, tuples and views copy; classes, list, dict, set are reference types, passed and returned by reference.
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.
No eval, monkey-patching, or runtime __dict__ mutation. Types and structure are fixed at compile time.
tpyTurboPython's own types (Own, int32, Span, ...) live in the tpy module — a file can pull them all in with from tpy import *.
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.