Principle:Nautechsystems Nautilus trader Hybrid Rust Cython Build
| Knowledge Sources | |
|---|---|
| Domains | Build_Tooling, Python, Rust |
| Last Updated | 2026-02-10 08:00 GMT |
Overview
A build architecture that compiles Rust core libraries and Cython extension modules in a coordinated two-phase process, producing a single installable Python package.
Description
The Hybrid Rust-Cython Build pattern addresses the challenge of distributing a Python package whose performance-critical internals are implemented in two compiled languages. NautilusTrader's architecture uses Rust for core domain logic (via static libraries and PyO3 bindings) and Cython for the Python-facing API layer that calls into those Rust libraries via C FFI. A standard Python build system (setuptools, poetry-core) cannot natively handle this two-language compilation, so a custom build script orchestrates the process. Phase 1 compiles Rust crates into static libraries and a PyO3 dynamic library. Phase 2 compiles Cython .pyx files into C extensions that link against the Rust static libraries. The result is a Python package where import nautilus_trader transparently loads both Cython and Rust-backed modules.
Usage
Apply this pattern when a Python project needs performance beyond what Cython alone provides, and the core domain logic benefits from Rust's type system, memory safety, and zero-cost abstractions. The two-phase build is necessary when Cython extensions need to call into Rust code via C FFI headers, requiring Rust libraries to be compiled first.
Theoretical Basis
The build follows a dependency-ordered compilation pipeline:
# Abstract algorithm (NOT real implementation)
# Phase 1: Rust
rust_libs = cargo_build(
crates=["core", "model", "backtest", "common", "persistence"],
features=["ffi", "cython-compat", "extension-module"],
profile=BUILD_MODE,
)
pyo3_lib = cargo_build(crate="pyo3", profile=BUILD_MODE)
copy(rust_libs, target_dir)
copy(pyo3_lib, target_dir)
# Phase 2: Cython (depends on Rust static libs)
pyx_files = discover("nautilus_trader/**/*.pyx")
for pyx in pyx_files:
c_ext = cythonize(pyx, compiler_directives=DIRECTIVES)
c_ext.link_against(rust_libs)
# Result: single Python package with both Cython and Rust modules
Key architectural decisions:
- Static linking for Rust libraries — avoids runtime library path issues
- Dynamic library for PyO3 — required for Python import mechanics
- Platform-specific flags — different compilers (clang/MSVC) and linker behaviors per OS