Key Points
There and Back Again
- Python and Rust are complementary when each has a clearly defined role.
- Start from a measured need before considering a rewrite.
- The integration boundary and its maintenance cost are part of the design.
Translating Python into Rust
- Familiar surface syntax can help us begin reading Rust.
- A Rust block can return its final expression; a semicolon discards
that expression’s value, while
returnexits explicitly. - Python quote style does not change its string type; Rust
distinguishes borrowed
&str, ownedString, and scalarcharvalues. - Rust closures use
|arguments| expressionand capture by borrow, mutable borrow, or value according to how they use their environment. - Rust makes mutability, data types, absence, and recoverable errors explicit.
- The
?operator propagates a typed failure; it neither handles the failure locally nor causes a panic. - Rust attributes provide compile-time instructions; Python decorators operate on runtime objects as definitions execute.
- A slice such as
&[i64]lets a function inspect sequential data without taking ownership of it.
Where the Analogy Ends
- Ownership determines who is responsible for a value and when it is released.
- Borrowing provides temporary access without transferring ownership.
- Lifetimes let the compiler verify relationships between references.
- Enums, traits, and concurrency checks are central Rust design tools.
- Prototypes may defer decisions, provided their shortcuts remain visible and are reviewed before production.
Adding Rust to Python Incrementally
- Migrate one well-tested, high-value component at a time.
- Minimize language crossings and data conversion.
- Keep the public Python API separate from the native implementation.
- Leaving a component in Python is a valid engineering outcome.
Building a Python Extension with PyO3
- Pixi makes the workshop toolchain reproducible; Cargo and Maturin build the native Python package.
- PyO3 exposes selected ACORN behavior without exposing the whole Rust crate.
- Distribution, import, and nested-module names are separate contracts.
- A build crosses distinct layers; diagnose the earliest layer that fails.
- Use a
#[pyclass]when validated state and related behavior must persist across Python calls. - The first working boundary is deliberately small: one string in and one boolean out.
From Python to Rust and Back Again
- Binding signatures reveal whether values are borrowed, owned, converted, or stored in Rust-backed Python objects.
- Expected Rust failures should become intentional Python exceptions.
- Layered tests make domain, binding, and packaging failures easier to locate.
- Property tests generate and shrink examples for invariants, round trips, and comparisons between the Python and Rust implementations.
- Test the real
acorn.schemaimport paths on every supported Python version. - A clean wheel smoke test catches packaging failures a development import can miss.
Practical Guidance and Q&A
- Production readiness includes packaging, observability, support, and team skills.
- Prototype shortcuts are useful when they are explicit and reviewed before release.
- Compiler checks reduce some testing burden; they do not replace behavioral and integration tests.
- Property testing explores broad input spaces; Miri and formal verification answer narrower questions with different guarantees and constraints.
- Use the integrated package for benchmarks.
- Incremental adoption should remain reversible until evidence supports it.
- The goal is a better Python project.