Summary and Schedule
Python makes it easy to turn an idea into useful software. Rust requires more explicit choices and can provide speed, predictable resource use, and strong compile-time guarantees. This workshop shows how to add Rust at a carefully chosen boundary in an existing Python codebase.
Starting from familiar scripts and notebooks, the workshop examines
where a direct translation into Rust stops working. We will build a
teaching-sized version of acorn-py
with PyO3 and Maturin and see where Rust already
appears in the Python ecosystem.
Who is this workshop for?
This workshop is for Python developers who are comfortable writing functions, using collections, importing packages, and working in a terminal. No previous Rust or systems-programming experience is required.
The lesson is especially useful if you want to:
- speed up a focused part of a Python application;
- make resource-heavy or concurrent code more predictable;
- distribute native functionality behind a familiar Python API; or
- understand the tradeoffs behind the growing number of Rust-backed Python packages.
Learning outcomes
By the end of the workshop, you will be able to:
- relate common Python constructs, including strings and anonymous functions, to their Rust counterparts;
- explain ownership, borrowing, traits, and compile-time guarantees in practical terms;
- identify a suitable boundary for introducing Rust into an existing Python project;
- expose Rust functions and types to Python with PyO3;
- build and install an extension module with Maturin;
- move data and errors across the Python-Rust boundary and test both sides; and
- choose between example tests, property tests, Miri, model checking, and proof-oriented verification for a specific engineering risk.
The worked example
acorn-py exposes selected APIs from the Rust crates
acorn-lib and acorn-schema as the Python
module acorn. We begin with the familiar Python contract
from acorn.schema.validate import is_doi, trace identifier
validation into Rust, add PyO3 functions and classes, translate failures
into Python exceptions, and finish by testing a stable-ABI wheel.
The completed project contains more bindings than can be taught in one workshop. Our incremental build follows its real architecture and tooling while concentrating on a representative path: validator function, nested module, Rust-backed identifier type, error mapping, tests, and packaging.
The seven episodes provide 180 minutes of active instruction, exercises, and discussion. Complete the setup before the session so the three hours can focus on design decisions and a working Python-to-Rust vertical slice.
The journey
| Stage | Focus | Destination |
|---|---|---|
| There and Back Again | Why combine Python and Rust? | A grounded reason to use both |
| Translating Python into Rust | Familiar syntax and concepts | A first mental map |
| Where the Analogy Ends | Ownership, borrowing, traits, and concurrency | Rust’s distinct value |
| Adding Rust Incrementally | Preserve acorn.schema.validate
|
A narrow acorn-py boundary |
| Building with PyO3 | Bind ACORN validators and types | Working acorn imports |
| Crossing the Boundary | Data, errors, and tests | A tested stable-ABI extension |
| Practical Guidance | Tradeoffs, assurance tools, next steps, and Q&A | A route home |
Why the boundary stays narrow
Rust adds a compiler toolchain, a native build, and a cross-language boundary. The workshop considers it only for focused work that may justify those costs and preserves a Pythonic interface throughout.
Start with the Setup instructions, then
follow the episodes in order. Keep the completed acorn-py
checkout as a reference while building the teaching version at each
checkpoint.
| Setup Instructions | Download files required for the lesson | |
| Duration: 00h 00m | 1. There and Back Again |
What does Rust add to a successful Python project? When is a mixed-language project worth the added complexity? Where is Rust already present in the Python ecosystem? |
| Duration: 00h 15m | 2. Translating Python into Rust |
How do familiar Python constructs appear in Rust? How do Rust expressions, semicolons, and return determine a
value?How do Python strings and lambdas compare with Rust strings and closures? Which differences are syntax, and which change the way we design programs? How are Rust attributes different from Python decorators? |
| Duration: 00h 50m | 3. Where the Analogy Ends |
What problems are ownership and borrowing designed to prevent? How do traits and enums shape Rust APIs? What does “fearless concurrency” mean in practice? Do Rust’s guarantees leave room for exploratory code? |
| Duration: 01h 15m | 4. Adding Rust to Python Incrementally |
Where should the Python-Rust boundary go? How can we preserve an existing Python API? How much work should cross the boundary in one call? What should stay in Python? |
| Duration: 01h 40m | 5. Building a Python Extension with PyO3 |
How does acorn-py expose Rust functions and nested modules
to Python?What do PyO3, Maturin, Cargo, and Pixi each provide? What happens between maturin develop and
import acorn?Why do the distribution, crate, and import names differ? |
| Duration: 02h 10m | 6. From Python to Rust and Back Again |
What happens to strings, paths, objects, and errors at the language
boundary? How do we choose conversions, exceptions, and tests for a binding contract? How can generated examples exercise invariants and round trips? How does acorn-py test its public Python
API?What does the stable-ABI wheel smoke test prove? |
| Duration: 02h 40m | 7. Practical Guidance and Q&A |
How do we take a mixed-language prototype toward production? How can we prototype in Rust without designing the whole system first? When should we use property testing, Miri, model checking, or deductive verification? Which maintenance and packaging questions should be answered early? What is the smallest useful next step? |
| Duration: 03h 00m | Finish |
The actual schedule may vary slightly depending on the topics and exercises chosen by the instructor.
Complete this setup before the workshop. We will use acorn-py
as one continuous example: first as a Python-facing
identifier-validation API, then as a Rust library exposed through PyO3,
and finally as a tested wheel built with Maturin.
The completed acorn-py repository is our reference
implementation. During the workshop we will build a smaller teaching
version of it one layer at a time.
Plan 20 to 30 minutes for the first setup. Pixi and Cargo must download the locked Python packages, Rust toolchain, and Rust crates before the first build.
What you need to install
Install these system-level tools:
-
Git, to cloneacorn-pyand fetch its pinned ACORN Rust dependencies; -
Pixi, which installs the project-specific Python, Rust, Maturin, pytest, Ruff, and native build dependencies; and - a platform linker and build tools, described below.
The locked environment already includes Python, Rust, Maturin, and
pytest, so you do not need to install them or create a separate virtual
environment. The committed pixi.lock and
Cargo.lock files define the workshop environment.
Check Git before continuing:
git --version
Install Pixi
Follow the official Pixi installation instructions for your operating system.
Open a new terminal so the updated PATH takes
effect.
Confirm that Pixi is available:
pixi --version
Get the reference project
Choose a directory where you keep source code, then clone
acorn-py:
git clone https://code.ornl.gov/research-enablement/acorn-py.git
cd acorn-py
Do not clone the larger ACORN repository separately. Cargo fetches
the acorn-lib and acorn-schema crates from the
immutable Git revision recorded in Cargo.toml and
Cargo.lock.
The names intentionally differ:
- the repository and Python distribution are named
acorn-py; - the Rust crate is named
acorn-py; and - Python code imports the extension as
acorn.
We will revisit that packaging boundary during the PyO3 episode.
Create the locked environments
From the root of the cloned acorn-py repository, install
both supported test environments:
pixi install --locked -e py310
pixi install --locked -e py313
These environments are declared in pyproject.toml. At
the time this lesson was prepared, they selected:
| Component | Project requirement |
|---|---|
| Python | 3.10 and 3.13 test environments; package minimum 3.10 |
| Rust | 1.96.x |
| Maturin | 1.9.x |
| PyO3 | 0.28.x with abi3-py310 and
extension-module
|
| pytest | 8.4.x |
Use the versions resolved by pixi.lock if its exact
patch versions differ from this table. Do not run
pixi update or cargo update during the
workshop.
Verify the development build
Build the extension into the Python 3.13 development environment and run the Python test suite:
pixi run -e py313 test
The test task creates a development environment, runs
maturin develop --uv --locked, and then runs pytest. The
first invocation also compiles the pinned ACORN crates and can take
several minutes.
Verify the Python-facing API directly:
pixi run -e py313 python -c "from acorn.schema.validate import is_doi; assert is_doi('10.11578/dc.20250604.1'); print('acorn-py is ready')"
The command should print acorn-py is ready.
Run the complete pre-workshop check
This sequence checks formatting and lints, tests the extension with
Python 3.10 and 3.13, builds its stable-ABI wheel, installs that wheel
into a clean environment, imports acorn, and checks the
acorn-py distribution metadata.
If a command fails, save the complete output and send it to an instructor before the workshop.
Network and managed-system requirements
Initial setup needs HTTPS access to Conda Forge, PyPI, crates.io, and
code.ornl.gov. The build does not require a separate ACORN
executable, model, service, credential, or local ACORN checkout.
On managed or offline systems, ask local support to allow or
pre-cache these dependencies. Linux hosts with an unusual glibc version
may also need a platform-specific Pixi system-requirements setting. Send
the output of pixi info to an instructor and leave the
committed lockfile unchanged.
Optional editor support
Any text editor works. For Visual Studio Code, the Python and
rust-analyzer extensions provide Python completion and inline Rust
compiler feedback. Opening the editor from
pixi shell -e py313 can help it discover the
project-managed tools.