Skip to content

Beyond the core

The guide so far is the core language: the type system, ownership, data modeling, control flow, and the build. This page is a short survey of everything else — what exists today and how mature it is. Compatibility is the single source of truth for exact status.

Async

Coroutines (async def), await, tasks, and asyncio-style entry points compile and run:

import asyncio

async def fetch(n: int) -> int:
    await asyncio.sleep(0.01)
    return n

async def amain() -> None:
    t = asyncio.create_task(fetch(40))
    u = asyncio.create_task(fetch(2))
    print(await t + await u)   # 42

asyncio.run(amain())

Limited today

The executor is single-threaded. Tasks, timers, wait_for, synchronization primitives, socket I/O, and client/server streams work. gather infers a common result type and returns a list. It cannot gather heterogeneous result types into a tuple. Compatibility tracks the exact boundary.

Threads

There is no GIL, and no threading module either. Threads are TurboPython's own API, modeled on Rust's threads. The spawn function takes an owned task and returns a handle to join. It checks at compile time that the task is safe to send across threads (Send):

from tpy.thread import spawn
from tpy import Int32

class Job:
    n: Int32
    def __init__(self, n: Int32):
        self.n = n
    def run(self) -> Int32:
        return self.n * 2

def main():
    h = spawn(Job(21))
    print(h.join())        # 42

main()

Cross-thread sharing goes through Arc (Ownership), and Atomic[T] covers shared counters. Mutex[T] and RwLock[T] guard shared mutable state, and Condvar blocks until a condition holds — Rust's std::sync primitives, from tpy.sync. The cross-thread and async-interop surface is still partial; Compatibility tracks what works today.

Generators and iterators

Generator functions, generator expressions, and hand-written iterators work. The one exception is yield from, which is not supported yet; the compiler rejects it with a diagnostic.

First-class functions

Functions are values. A function can be passed by name, a lambda bound wherever a callable is expected, and a nested def can close over the enclosing frame's locals — nonlocal makes a captured local mutable.

Two callable types mark the trade-off. Fn[[A], R] is a zero-cost callback for a parameter, inlined like a C++ template with no allocation or indirection. Callable[[A], R] is a type-erased callable — a std::function — that can also live in a field, a return, or a container, where the concrete function is not known until runtime. A lambda infers its parameter types from the type the context expects.

Protocols and dynamic dispatch

Structural protocols work and are static by default (Data modeling). @dynamic opts a protocol into runtime dispatch — the compiler's own suggested fix wherever an override would have dispatched dynamically under CPython.

Limited today

A protocol type cannot yet be a container element (list[Speaker] is rejected), which limits the classic heterogeneous-collection pattern. Compatibility tracks this.

Enums

enum.Enum and IntEnum work, with auto() values, .name and .value, identity and equality comparison, iteration over the members, and value or name lookup (Color(0), Color["red"]). A match can branch on enum members, and an enum compiles to a C++ enum class.

Network, processes, and the wider stdlib

An HTTP client exists in early form (tplib.requests), and TLS works for both HTTPS clients and servers. subprocess and multiprocessing do not exist. For a services stack, the missing pieces are still large; Compatibility is the place to check before planning a port.

Native interop

Native interop runs in two directions. A TurboPython module compiles into a CPython extension (# tpy: ext_module, Building). Existing C++ binds into TurboPython with @native, mapping a class or function onto a C++ type or call — the standard library itself is built this way. @native is usable but undocumented; the API may change.

Macros

A compile-time macro mechanism exists inside the compiler — several built-in decorators are implemented as compile-time macros. A user-facing macro system is not documented or supported yet. Compatibility records its status.

This closes the core guide. For readers coming from a systems language, Coming from C++/Rust maps the language onto C++ and Rust terms. Getting started and Compatibility cover the practical side, and the map gives the overview again at any time.