Skip to content
Internals · Annex R · Technical preview Technical preview

Rust extension

Python remains the behavioural reference. Rust accelerates the byte copy between pipeline stages when the extension is available — nothing else.

Optional backend · not required · native wheels for CPython 3.12 and later · describes Cuprum 0.2.0-beta2

Plate 05 In the basement annex, Copper keeps a paw on the bypass valve. The copper line is optional; the teal line always runs.

01 — What is optional

Optional, and narrow

Cuprum is complete without Rust. The extension speeds up one operation, and says so when it declines.

Cuprum’s streams normally move through asyncio in 64 KiB reads. For a few kilobytes that costs nothing worth measuring. For gigabytes flowing through a multi-stage pipeline, every chunk is a trip through the event loop and a fresh bytes object, and the extension exists to take that copy off the loop.

It is a separate compiled module that Cuprum imports if it can. A pure-Python install behaves identically, reports is_rust_available() as False, and never needs a Rust toolchain.

Fig. 01

What is optional, and what falls back?

Technical preview
  1. Stage n upstream stdout An OS pipe owned by asyncio.
    passes raw descriptors to the next stage
  2. Selection CUPRUM_STREAM_BACKEND auto uses Rust if the extension imports; python never does; rust requires it.
    passes eligible hop to the next stage
  3. Optional path Rust pump Copies outside the GIL in an executor; splice() on Linux.
    passes bytes to the next stage
  4. Stage n + 1 downstream stdin Receives identical bytes whichever pump ran.
  • Hop cannot be handed over safely The hop runs on the Python pump and the reason is recorded: a DEBUG record on cuprum._pipeline_streams and, if registered, a decline metric.
  • Windows Every hop declines with platform_unsupported and uses the Python pump. A raw overlapped handle handed straight to the native shim is rejected, and counted as windows_raw_handle_rejected, rather than used. Pipelines stay correct.
  • rust selected but not installed Pipeline execution raises ImportError rather than silently falling back.
Only the inter-stage copy has a native path. Capture, echo, line events, spawning, policy, and results are the same Python code on every backend. Source: docs/users-guide.md#choosing-a-stream-backend and ADR-001 at v0.2.0-beta2

02 — Capabilities

What runs where

Available, integrated, implemented-but-inert, and proposed are different states, and the matrix keeps them apart.

Capability matrix · technical preview · Cuprum 0.2.0-beta2
Capability Python path Rust extension Notes
Inter-stage pipeline pumping AvailableIntegratedUsed under auto or rust when the extension imports. splice() on Linux, a read/write loop on macOS. Each hop that cannot be handed over safely runs on the Python pump instead.
Pumping on Windows AvailablePython onlyAsyncio’s Proactor pipes use overlapped handles the synchronous native pump cannot safely use, so every hop declines with platform_unsupported. A raw overlapped handle handed straight to the native shim is rejected and recorded as windows_raw_handle_rejected rather than used.
Capturing stdout and stderr AvailableImplemented, not integratedrust_consume_stream exists and is tested, and tests fail if production code routes through it. Integration waits on a measured 20% wall-time win (roadmap phase 6).
Echo and tee to sinks AvailableProposedA raw-sink helper for qualifying sinks, to be profiled before it is built (roadmap phase 7).
Line callbacks and line events AvailablePython onlyDecoding and per-line dispatch stay in Python whichever backend is selected.
Spawning and orchestrating processes AvailableUnder investigationA synchronous native subprocess path and native pipeline orchestration are open decisions (roadmap step 8.3). Process management is Python today.
Native wheels AvailableAvailablePublished with 0.2.0b2 for glibc-based Linux (x86_64, aarch64), macOS (x86_64, arm64), and Windows (x86_64), one cp312-abi3 wheel per platform for CPython 3.12 and later. Other platforms and free-threaded builds install the pure-Python wheel; Windows arm64 is planned (roadmap 8.5.1).
  • Available
  • Integrated
  • Implemented, not integrated
  • Proposed
  • Under investigation
  • Python only

03 — Backend selection

Choosing a backend

One environment variable, read once per process.

CUPRUM_STREAM_BACKEND takes three values. auto, the default, uses Rust when the extension imports and Python otherwise. python forces the pure path, for debugging or to reproduce a pure-Python install. rust requires the extension and fails loudly without it, for benchmarking.

The choice is resolved on first use and cached for the life of the process, so set it before Cuprum runs anything. Changing it later has no effect.

shell Force a backend
$ CUPRUM_STREAM_BACKEND=python python my_script.py
$ CUPRUM_STREAM_BACKEND=rust python my_script.py
check_backend.py Probe
import cuprum

print(cuprum.is_rust_available())
Verified 2026-10-19 · Cuprum 0.2.0b2 · native and pure-Python wheels
Expected outputnative wheel
True
Expected outputpure-Python wheel
False

Watching it decline

A hop that falls back is a routing decision, not an error, so it is logged at DEBUG on cuprum._pipeline_streams with a cuprum_reason: raw_fd_unavailable, reader_unresumable, reader_pause_failed, blocking_mode_unavailable, duplicate_fds_unavailable, or platform_unsupported. To count them instead, register PumpMetricsHook with observe_pump; its labels are drawn from that closed set, so the series count is fixed.

04 — Parity and limits

How parity is checked, and what is not claimed

Parity

The stream tests run on both backends and assert identical output for empty streams, multibyte UTF-8 split across chunk boundaries, downstream stages that exit early, and backpressure under large payloads. Hypothesis generates random payloads and chunk boundaries and checks every byte survives either pump.

The unsafe Rust is confined to audited boundary crates, with Verus, Kani, and Miri evidence archived, and the native pump’s lifecycle is modelled with Loom. Since 0.2.0-beta1, Miri runs across more of the stream boundary, and a RustStreamError enum at the PyO3 boundary maps every native failure to the same Python exception the pure path would raise, so both backends fail the same way.

What this page does not claim

No speed-up figure is quoted here. The users’ guide describes multi-fold throughput gains on large pipe-to-pipe transfers and negligible differences on small outputs, but a number without its workload, machine, baseline, and sample size would be decoration. CI benchmarks every push to main and every pull request that touches performance-relevant code, and gates on the Rust-to-Python ratio; published measurements with that context are pending.

To measure your own workload, run make benchmark-e2e in a checkout. It covers three payload sizes, two pipeline depths, and runs with and without line callbacks, on each available backend.

Issues

Send feedback

Tried the native path? Report what you measured, or what declined, on the issue tracker.