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 return exits explicitly.
  • Python quote style does not change its string type; Rust distinguishes borrowed &str, owned String, and scalar char values.
  • Rust closures use |arguments| expression and 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.schema import 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.