pyroxide
Warning: This is AI slop. Built in one session with Claude. Use at your own risk and peril.
Zero-copy FFI bridge between Rust and Mojo — the glowing bridge between oxidation and fire.
use *;
mojo_type!
unsafe extern "C"
let v = Vec3 ;
let len = unsafe ;
Overview
Pyroxide lets Rust and Mojo share data with zero copies. Define types once in Rust, pass pointers to Mojo, get results back — no serialization, no allocation.
| Crate | Purpose |
|---|---|
pyroxide |
Core bridge: types, traits, handles, error handling |
max-sys |
Raw bindgen bindings to the Modular MAX C API |
Why no
mojo-sys? Mojo has no C SDK. You call Mojo via@export→ shared library →extern "C".max-sysis for the MAX inference engine, which does have a C API.
| Module | What |
|---|---|
bridge |
IntoMojo, FromMojo, MojoRef, MojoMut, MojoSlice, MojoSliceMut |
abi |
ABI type mapping docs, OutParam |
trampoline |
catch_mojo_call (panic-safe FFI) |
string |
MojoStr (ptr+len for FFI) |
types::max |
DType, Tensor<T>, TensorView<T>, TensorDescriptor, TensorShape |
Prerequisites
Quick start
Add to your Cargo.toml:
[]
= "0.1"
Or with MAX tensor types:
[]
= { = "0.1", = ["max"] }
Run the examples:
Mojo files are compiled automatically by build.rs — no manual steps.
Examples
Progressive tutorial — each builds on the previous.
| # | Example | What you learn |
|---|---|---|
| 01 | hello |
Raw FFI: call one Mojo function |
| 02 | structs |
Pass #[repr(C)] structs, read and mutate |
| 03 | tensors |
TensorDescriptor, sum, dot, matmul |
| 04 | simd |
Mojo's explicit SIMD: ~8x speedup |
| 05 | dtype_generic |
One Mojo template → f32/f64/i32 |
| 06 | comptime |
Compile-time unrolling, baked constants |
| 07 | embeddings |
HuggingFace model → Mojo inference → similarity matrix |
| 08 | abi_edge_cases |
Bool, Int boundaries, Float64 specials, OutParam |
| 09 | image_blur |
Large mutable buffer, MojoSliceMut |
| 10 | tokenizer |
MojoStr string passing, variable-length output |
| 11 | neural_layer |
Linear + ReLU + softmax, 4 TensorDescriptors |
| 12 | accumulator |
Stateful struct, repeated MojoMut across calls |
| 13 | sorting |
In-place sort + reverse, verify against Rust |
| 14 | mandelbrot |
Compute-heavy grid, ASCII visualization |
| 15 | nested_structs |
Line (2 Points), Triangle (3 Points), centroid |
| 16 | struct_arrays |
&[Point], particle simulation, kinetic energy |
| 17 | mixed_args |
6 args: pointers + scalars + bools in one call |
| 18 | padding |
u8 + f64 + i32 struct with alignment padding |
| 19 | bytes |
u8/i64 arrays, XOR, prefix sum |
| 20 | call_overhead |
Noop/identity benchmark, pointer stability |
| 21 | edge_cases |
Zero-length, NaN in structs, byte roundtrip |
| 22 | large_data |
1M-element dot, scale-add, reduce-max |
| 23 | chained |
Normalize→argmax pipeline, NaN/-1 sentinels, histogram |
| 24 | matrix |
Transpose, Hadamard product, trace |
| 25 | catch_panic |
catch_mojo_call, string output, panic recovery |
| 26 | pipeline |
Enum-like dispatch, multi-step transforms |
How it works
Rust side
use *;
mojo_type!
unsafe extern "C"
let p = Particle ;
let energy = unsafe ;
Mojo side
@export
def compute_energy(addr: Int) -> Float64:
var p = UnsafePointer[Float64, MutExternalOrigin](unsafe_from_address=addr)
var vx = p[3]
var vy = p[4]
var vz = p[5]
var mass = p[6]
return 0.5 * mass * (vx*vx + vy*vy + vz*vz)
Cost
| Operation | Overhead |
|---|---|
v.as_raw() |
~1ns (pointer cast) |
v.as_raw_mut() |
~1ns |
MojoSlice::new(&data).as_raw() |
0 copies (read-only slice) |
MojoSliceMut::new(&mut data).as_raw() |
0 copies (mutable slice) |
MojoStr::new(s).as_raw() |
0 copies (string → ptr+len) |
TensorDescriptor |
0 copies |
OutParam::call2(|| ...) |
0ns overhead (MaybeUninit) |
catch_mojo_call(|| ...) |
0ns on success |
Safety
- Dangling pointers:
MojoRef<'a, T>ties pointer lifetime to the Rust borrow - Panics across FFI:
catch_mojo_callcatches panics (unwinding acrossextern "C"is UB) - Layout mismatch:
mojo_type!enforces#[repr(C)]at compile time - Ownership: Rust owns, Mojo borrows — documented and enforced by types
Feature flags
| Flag | Enables |
|---|---|
max |
DType, TensorShape, TensorDescriptor, Tensor<T>, TensorView<T>, MojoDType |
Status
Early stage. API will change.
| Component | Status |
|---|---|
mojo_type!, IntoMojo/FromMojo |
31 unit tests + 26 examples |
MojoSlice / MojoSliceMut |
Tested across 15+ examples |
MojoStr |
Tested (tokenizer, string output) |
OutParam |
Tested (divmod, ABI edge cases) |
catch_mojo_call |
Tested (panic recovery example) |
Tensor<T> / TensorView<T> |
Tested with HuggingFace + neural layer |
abi module |
Empirically verified against Mojo 0.26 |
Proc macros (#[mojo_fn]) |
Not implemented — deferred to 0.2.0 |
Non-goals
- Async bridging. Incompatible runtimes. Use
spawn_blockingfor async Rust. - GIL / runtime token. Mojo has no GIL. Thread safety is your responsibility.
- Mojo codegen. No
.mojogeneration from Rust types. Write both sides by hand.
Project layout
pyroxide/ Core library (31 unit tests)
max-sys/ Bindgen from MAX C headers (8 headers, 131 bindings)
examples/
mojo/ Mojo source files (auto-compiled by build.rs)
examples/ 26 Rust example binaries
design/ 13 Architecture Decision Records
scripts/
fetch-headers.sh Download MAX C headers from GitHub
pre-commit Git pre-commit hook
check-docs.sh Verify docs stay in sync