Skip to content

The typhon_runtime module

The compiler generates a small typhon_runtime/ package alongside your code whenever the emitted Python references any of its helpers — that is, whenever the program uses Result / Ok / Err, go (spawn), lazy import / lazy let, map_pure, deep_freeze, checked_cast (the as! cast, v0.14.0), or try_result (v0.15.0). This page lists what’s in it. The exact source below is what tyc build writes verbatim — search for TYPHON_RUNTIME_*_PY in tyc/crates/tyc/src/commands/build.rs to read the templates.

typhon_runtime/__init__.py

Always emitted when any of the runtime helpers are needed. Re-exports Ok / Err / Result so callers can from typhon_runtime import Ok, Err, and re-exports the lazy, tasks, result, parallel, stdlib submodules under dotted paths.

# generated by tyc — do not edit
from __future__ import annotations
from dataclasses import dataclass
from typing import Generic, TypeVar
from . import lazy, parallel, result, stdlib, tasks # re-exported for `typhon_runtime.<sub>.…`
_T = TypeVar("_T")
_E = TypeVar("_E")
@dataclass(slots=True, frozen=True)
class Ok(Generic[_T]):
value: _T
@dataclass(slots=True, frozen=True)
class Err(Generic[_E]):
error: _E
# Use `typing.Union` rather than PEP 695 `type Result[T, E] = …` so the
# generated runtime loads under Python 3.10 / 3.11 / 3.12 as well as the
# 3.13+ default.
from typing import TypeAlias, Union
Result: TypeAlias = Union[Ok, Err]
__all__ = ["Ok", "Err", "Result", "tasks", "lazy", "stdlib", "result", "parallel"]

Ok and Err are declared in __init__.py rather than in a result.py submodule. The result submodule next to it holds combinator helpers (map, map_err, unwrap_or, …) — see result.py below.

typhon_runtime/tasks.py

Emitted whenever go appears. Strong-reference task registry so the event loop’s weak-ref behaviour can’t GC a task mid-flight.

# generated by tyc — do not edit
"""Strong-reference task registry for the `go` keyword."""
from __future__ import annotations
import asyncio
from typing import Awaitable, TypeVar
_T = TypeVar("_T")
_BACKGROUND: set[asyncio.Task] = set()
def spawn(coro: Awaitable[_T]) -> asyncio.Task[_T]:
"""Schedule *coro* and hold a strong reference until it finishes."""
task = asyncio.create_task(coro)
_BACKGROUND.add(task)
task.add_done_callback(_BACKGROUND.discard)
return task

For [python] free-threaded = true, the same module exposes a sibling ThreadPoolExecutor-backed path under typhon_runtime.parallel.

typhon_runtime/lazy.py

Emitted whenever lazy import or lazy let appears. Backs both the lazy-import proxy and the sentinel-cached lazy-value proxy.

# generated by tyc — do not edit
"""Helpers backing the `lazy import` and `lazy let` Typhon keywords."""
from __future__ import annotations
import importlib
import importlib.util
import threading
from types import ModuleType
from typing import Callable, TypeVar
_T = TypeVar("_T")
class _LazyModule:
"""Attribute-proxy that imports its underlying module on first access."""
__slots__ = ("_name", "_module")
def __init__(self, name: str) -> None:
object.__setattr__(self, "_name", name)
object.__setattr__(self, "_module", None)
def _load(self) -> ModuleType:
module = object.__getattribute__(self, "_module")
if module is None:
module = importlib.import_module(object.__getattribute__(self, "_name"))
object.__setattr__(self, "_module", module)
return module
def __getattr__(self, attr: str) -> object:
return getattr(self._load(), attr)
def lazy_import(name: str) -> _LazyModule:
"""Return a module proxy that defers loading until first attribute access."""
return _LazyModule(name)
class _LazyValue:
"""Proxy that materialises an underlying value on first use."""
__slots__ = ("_factory", "_value", "_lock")
def __init__(self, factory: Callable[[], _T]) -> None:
object.__setattr__(self, "_factory", factory)
object.__setattr__(self, "_value", _UNSET)
object.__setattr__(self, "_lock", threading.Lock())
def _materialise(self) -> object:
value = object.__getattribute__(self, "_value")
if value is _UNSET:
with object.__getattribute__(self, "_lock"):
value = object.__getattribute__(self, "_value")
if value is _UNSET:
factory = object.__getattribute__(self, "_factory")
value = factory()
object.__setattr__(self, "_value", value)
return value
def __getattr__(self, name: str) -> object:
return getattr(self._materialise(), name)
# `__call__`, `__getitem__`, `__iter__`, `__str__`, `__bool__`, `__eq__`,
# `__hash__`, `__len__` all also forward to the materialised value so the
# proxy is mostly transparent.
_UNSET = object()
def lazy_let(factory: Callable[[], _T]) -> _T:
"""Return a proxy that calls *factory* on first attribute access."""
return _LazyValue(factory) # type: ignore[return-value]

The proxy intentionally implements __str__ / __bool__ / __eq__ / __len__ etc. so plain print(CFG) and if CFG: work as you’d expect against the materialised value rather than landing on the proxy’s <lazy: unmaterialised> debug repr.

typhon_runtime/result.py

Emitted whenever Result, Ok, or Err appears. Holds the combinator surface — .map(f), .map_err(g), .and_then(f), .or_else(h) — as methods directly on the Ok / Err runtime classes (since v0.6.0); Ok and Err themselves live in __init__.py so from typhon_runtime import Ok, Err works without traversing the submodule.

Since v0.9.0 the in-process VM (tyc run) also exposes these methods natively — NativeFn wrappers capture the receiver and dispatch the right combinator semantics — so Ok(7).and_then(double) works under both tyc run and tyc build && python build/main.py. Before v0.9.0 the methods existed only in the emitted result.py, so a typecheck-clean program crashed under tyc run with AttributeError: Ok has no attribute 'and_then'.

Since v0.13.0 the module also exposes the unwrap / query family — .unwrap(), .expect(msg), .unwrap_or(default), .unwrap_or_else(f), .ok() (→ T?), .err() (→ E?), .is_ok(), .is_err() — on Ok / Err. The Result method surface is now closed: an unknown method fires tyc::attribute_not_found at check time instead of crashing at runtime.

Since v0.15.0 the try_result(thunk[, on_err]) exception→Result combinator is also exported from the package root — from typhon_runtime import try_result. It runs thunk() and returns Ok(result); on any exception it returns Err(on_err(exc)), or Err(exc) when no mapper is given. In source it is a prelude name (no import needed, like Ok / Err / Result); tyc build auto-injects the import, and the VM registers it as a prelude native.

typhon_runtime/freeze.py

Emitted whenever freeze let appears (Phase 6, v0.3.0). Recursively converts list → tuple, dict → MappingProxyType, set → frozenset; descends into nested values; raises TypeError at startup on anything without a clean immutable equivalent (file handles, sockets, generators, non-frozen dataclasses). Frozen dataclasses pass through unchanged. On CPython 3.15+ a builtin frozendict (PEP 814) is treated like MappingProxyType: kept as a frozendict, with its values frozen. When [python] target is 3.15 or later, the generated helper turns a dict into a frozendict rather than a MappingProxyType.

Since v0.9.0 the type checker also pre-validates the freeze let X = <expr> RHS at check time via tyc::freeze_not_freezable, so non-frozen user-class constructors fail at tyc check instead of at first import — the deep-freeze raising TypeError at runtime is now a backstop for cases the static check can’t see through, not the primary error surface. The VM also runs the freeze pass since v0.9.0; before v0.9.0 tyc run treated freeze let as a regular let and mutations through aliased references silently succeeded.

The full implementation lives in tyc/crates/tyc/src/commands/build.rs under TYPHON_RUNTIME_FREEZE_PY.

typhon_runtime/cast.py

Emitted whenever the as! checked boundary cast appears (v0.14.0). Holds checked_cast(value, tp), which backs EXPR as! TYPE: it performs a recursive structural shape check of value against the target type tp and returns value on success, raising TypeError on a mismatch. The check honours int→float widening (an int passes a cast to float) and recurses into containers, unions, and parametric types (x as! int | None, d as! dict[str, int]).

as! lowers to a __typhon_checked_cast__(value, tp) call routed through this helper on the build path. Under tyc run the VM intercepts __typhon_checked_cast__ before argument evaluation and applies a structural check natively, so a cast composes in any expression position (v0.15.0) under both runners. The VM’s check does not yet cover Literal, newtype, generic-alias or interface targets; see the supported-target table.

typhon_runtime/traceback.py

Emitted only when [emit] traceback-remap = true (v0.14.0, default off). Holds install(), which sets a sys.excepthook that rewrites the frames of an uncaught exception’s traceback from emitted .py lines back to .ty lines via the .py.map sidecars — the automatic counterpart to the manual tyc trace. When the knob is on, tyc build injects a typhon_runtime.traceback.install() call into the entry module’s __main__ block so it runs before any user code.

typhon_runtime/parallel.py and typhon_runtime/stdlib.py

parallel.py carries the ThreadPoolExecutor-backed helpers used when [python] free-threaded = true and [strictness] auto-parallel = true are both set. stdlib.py carries Typhon’s tiny native stdlib (helpers that the desugarer expects to find on import — kept small on purpose, since the runtime is shipped with every project). Both are always emitted alongside __init__.py so a single set of submodules covers any combination of features the project may grow into without re-running tyc build to materialise them.

Why generated source, not PyPI?

Two reasons:

  1. Deployment story. A Typhon-compiled wheel ships with these files in it. Production servers run vanilla CPython plus your code; no pip install typhon-runtime step. There is no shared dependency that can drift between Typhon-compiled wheels.
  2. No version coupling. Each project’s typhon_runtime/ is exactly the version that compiled it. Upgrading tyc doesn’t break existing wheels in flight.

The cost (~150 lines of generated Python per project) is negligible compared to the deployment-clarity benefit.

Customising

You can’t. typhon_runtime/ is generated; it gets overwritten on every tyc build. If you need to customise, write a wrapper module that imports and re-exports.

Where next