Skip to content

Coming from C++/Rust

TurboPython targets the same kind of efficient, compiled native code as C++ and Rust, but with ergonomics much closer to Python. It lets a Python developer write efficient native software in Python's own syntax and model, without switching to a systems language.

This page maps TurboPython's constructs to the C++ and Rust terms they correspond to. It assumes familiarity with one of those languages; Python readers can skip it.

In TurboPython, every object has exactly one owner. Durable storage owns what it holds: a field owns its value, a container owns its elements, a global owns its object. Local variables and parameters own nothing — they refer to objects the way names do in Python, and several of them can refer to the same object at once.

The compiler does not restrict this aliasing the way Rust's borrow checker does. There are no lifetime annotations, and no rule against aliasing a value while it can still be mutated. The one thing it checks is escape: a reference must not outlive its owner, so a function cannot return or store a reference to something that will be freed once the call ends.

The mapping

TurboPython C++ Rust
T parameter const T& / T& &T / &mut T
T return T& &T
Own[T] parameter T&& T (moved)
Own[T] return T T (moved)
readonly[T] const T& &T
Ptr[T] T* *mut T
Span[T] std::span<T> &mut [T]
Span[readonly[T]] std::span<const T> &[T]
copy() copy constructor .clone()
@nocopy deleted copy ctor no Clone
A \| B std::variant enum
T \| None std::optional<T> Option<T>
match tag dispatch match
int BigInt (arbitrary precision) num-bigint
Int32 / Int64 int32_t / int64_t i32 / i64
str std::string_view / std::string Cow<str>
String std::string String
StrView std::string_view &str
class struct struct
raise / except exceptions (no analog)
panic std::exit(1) panic!
Rc / Arc shared_ptr / weak_ptr Rc / Arc
Send Send

Behavior worth knowing

Plain returns. A function that returns a reference type by plain -> T hands back a reference (T&), not a copy; the caller borrows the result. Value types return by value. To return an owned reference-type value, annotate the return Own[T]. Returning a reference-type local by plain T is rejected, because that reference would dangle once the frame is gone (Ownership).

Moves, not boxes. Own[T] transfers ownership. As a parameter it lowers to an rvalue reference (T&&), and the caller gives up its value; as a return it passes the value straight out. This is Rust's by-value move — no heap allocation, nothing boxed.

Implicit copies. When a value is stored into durable storage and the source is used again afterward, TurboPython makes a deep copy instead of a move. C++ would copy silently; Rust would reject the program. TurboPython copies, and warns at the spot. Marking the type @nocopy turns that warning into an error. When the source is not used again, the store is a move and nothing is copied (Efficiency).

Borrows and mutation. Mutating a container while a reference into it is still live is allowed, but the compiler warns ('append' may invalidate references) — the hazard C++ knows as iterator invalidation, and the one Rust prevents outright with &mut.

Const-correctness is inferred. A parameter a function never mutates is passed as const T& automatically, so the readonly[T] annotation is rarely needed. This is C++ const-correctness, or Rust's split between & and &mut, without spelling it out (Functions).

Arbitrary-precision int. A bare int is not a machine word; it is an arbitrary-precision integer, the kind C++ and Rust reach for a library to get. A value that fits in 63 bits stays inline in a single word, so ordinary-sized integers allocate nothing; only larger values move to the heap. The fixed-width machine types are Int32, Int64, and their unsigned siblings, which map to int32_t/int64_t and i32/i64 (Types).

No null dereference. T | None is an explicit option type (std::optional<T> / Option<T>). A value that might be None must be narrowed before its T operations are allowed, and the compiler enforces that — there is no null pointer to forget to check (Control flow).

Static dispatch. Method calls bind at compile time. A call through a base-class reference does not dispatch virtually; it calls the static type's method, and an override that hides a base method warns. Runtime polymorphism is opt-in, through structural protocols (TurboPython's interfaces), not through class inheritance (Data modeling).

Deterministic destruction. An object is destroyed when its owner releases it, as with C++ RAII or Rust's Drop. __del__ is that destructor: it runs at a defined point, not whenever a garbage collector chooses.

Monomorphized generics. A generic function or class compiles once per concrete type, like a C++ template or a Rust generic. def first[T](xs: list[T]) -> T (PEP 695 syntax) produces a separate specialization for each T it is used with.

Real C++ exceptions. raise and except compile to real C++ exceptions: the non-throwing path costs nothing, and a raise unwinds the stack. The @error_return decorator instead propagates errors as values, compiling to std::expected — Rust's Result — for hot paths. The compiler applies it automatically to every __next__, so a loop ends on StopIteration with no exception-throwing cost. Panics are separate — overflow, a failed narrowing, and similar unrecoverable errors print a message and exit the process, like a Rust panic! with no catch_unwind (Control flow).

Predictable codegen. A class becomes a plain struct; list[Float64] becomes a contiguous std::vector<double>; an Own[T] parameter becomes an rvalue reference. tpyc --dump-code prints the generated C++, which shows exactly what each construct became (Building).

Threads follow Rust's model

Thread spawning takes ownership of the task and enforces Send at the boundary, as Rust does. Threads do not borrow from the spawning frame, and the join handle returns the result. There is no GIL. Sharing data across threads goes through Arc — an atomic reference count, like Rust's — which shares without locking and does not by itself prevent data races. Atomic[T] covers counters and flags, and Mutex[T], RwLock[T], and Condvar mirror Rust's std::sync types (std::mutex, std::shared_mutex, and std::condition_variable in C++). Compatibility tracks the current state of the concurrency primitives.

The thread API and a worked spawn/join example are on Beyond the core, which covers the rest of the concurrency support.