Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Quick start

This walks you through your first xpile transpile.

Prerequisite

cargo install xpile

See Installation for alternatives.

1. A Python file

Save the following as factorial.py:

def factorial(n: int) -> int:
    return 1 if n <= 1 else n * factorial(n - 1)

2. Transpile to Rust

$ xpile transpile factorial.py
// xpile-generated from Python module factorial

// xpile-contract: C-PY-INT-ARITH
pub fn factorial(n: i64) -> i64 {
    if (n <= 1i64) { 1i64 } else {
        (n).checked_mul(factorial(
            (n).checked_sub(1i64).expect("xpile: i64 subtraction overflow; bigint promotion (contract C-PY-INT-ARITH slow path) not yet implemented")
        )).expect("xpile: i64 multiplication overflow; bigint promotion (contract C-PY-INT-ARITH slow path) not yet implemented")
    }
}

Note the // xpile-contract: C-PY-INT-ARITH citation and the .checked_*().expect(...) wrappers. Every arithmetic operation preserves the C-PY-INT-ARITH contract: i64 overflow panics with a pointer to the unimplemented bigint slow path, rather than silently wrapping the way native i64 arithmetic would.

You can type-check the output directly:

$ xpile transpile factorial.py --out factorial.rs
$ rustc -O factorial.rs --crate-type lib --emit=metadata --out-dir .

--emit=metadata type-checks and stops; it produces no runnable artifact, so this pair proves the emit compiles, not that it computes anything. Through v0.1.618 the second line was written -o /dev/null, which exits 1 on any host where the invoking user cannot write to /dev — i.e. essentially every reader’s: rustc puts its temp dir beside the -o path, and reports error: couldn't create a temp dir: Permission denied (os error 13) at path "/dev/rmeta…". That is an environment error wearing a compile error’s clothes, and this repository had already diagnosed it once, in its own sweep harness, and written the correction down — “correct invocation is --out-dir” (PMAT-1446, CHANGELOG [0.1.618]) — two months after this page started telling readers to run the broken spelling.

CI runs the compile-and-execute path on the README.md copy of the transcript above, not on this page’s copy: crates/xpile/tests/readme_quickstart_witness.rs parses the two blocks out of README.md, transpiles, compiles with rustc -O, and executes to assert factorial(10) == 3628800 and that factorial(21) panics naming C-PY-INT-ARITH rather than wrapping. The three published copies of that transcript — README.md, this page, and Tutorial: Python → Rust — are byte-identical as measured on 2026-07-31, but nothing enforces it: only the README.md copy is gated, so a correction applied there can leave these two behind. That is a measurement, not an invariant; the gate is specified as XPILE-BOOKTRANSCRIPT-001 in docs/roadmaps/queue.yaml next_lane. Before PMAT-1415 the paragraph claimed the execution with nothing behind it at all: the test that asserted 3628800 read the -> BigInt fixture, a different program whose emit has no checked_ call to overflow.

3. Same source, different backends

$ xpile transpile factorial.py --target ruchy
// xpile-generated from Python module factorial

// xpile-contract: C-PY-INT-ARITH
fun factorial(n: i64) -> i64 {
    if (n <= 1i64) { 1i64 } else {
        (n).checked_mul(factorial(
            (n).checked_sub(1i64).expect("xpile: i64 subtraction overflow; bigint promotion (contract C-PY-INT-ARITH slow path) not yet implemented")
        )).expect("xpile: i64 multiplication overflow; bigint promotion (contract C-PY-INT-ARITH slow path) not yet implemented")
    }
}

$ xpile transpile factorial.py --target lean
-- xpile-generated from Python module factorial

/-- xpile-contract: C-PY-INT-ARITH -/
def factorial (n : Int) : Int :=
  if (n <= (1: Int)) then (1: Int) else (n * (factorial (n - (1: Int))))

Three different targets, the same governing contract — and you can see it in all three, because all three emit it: // xpile-contract: on the Rust and Ruchy lanes, /-- xpile-contract: … -/ as a Lean docstring, which is the form lean will actually parse (see Tutorial: Python → Lean for why the attribute spelling was retired). Lean’s Int is unbounded, so C-PY-INT-ARITH is satisfied by construction — no overflow checks emitted, because there is no overflow.

Through v0.1.618 both transcripts above were shown without their header and citation lines, and the Ruchy one shortened its two panic messages to "..." — so the two blocks offered as evidence for “the same governing contract” were the two with the contract deleted, on the page a first-time reader reaches first. They were born that way in the commit that created this book (2026-05-20, PMAT-446), in which the Rust block on this same page did show the citation; the emitter never changed under them. Both are now the live emit of the shipped binary as measured on 2026-07-31 — the Lean block byte-for-byte — with the sole exception that the Ruchy if is reflowed to fit the page (the binary prints it on one line) — the same reflow the Rust block above uses, and the only difference between what is printed here and what the binary writes.

4. Inspect the substrate

$ xpile info
$ xpile diamond     # works anywhere — the contracts are compiled in
$ xpile quorum      # needs a checkout (reads docs/roadmaps/roadmap.yaml)

See the CLI reference for everything xpile can do, and Installation for the measured per-command table. Through v0.1.618 both lines above read # if you're in a repo with contracts/, which was false for xpile diamond — it exits 0 in an empty directory, README.md had always said so, and xpile quorum’s own error text said so too — and named the wrong cause for xpile quorum, whose blocker is the development ledger rather than contracts/.

5. Runnable examples (library API)

The repository ships six runnable examples under crates/xpile/examples/ that use the library API instead of the CLI:

$ git clone https://github.com/paiml/xpile && cd xpile
$ cargo run --example 01_python_to_rust   -p xpile  # factorial → Rust
$ cargo run --example 02_python_to_lean   -p xpile  # factorial → Lean
$ cargo run --example 03_python_to_ruchy  -p xpile  # gcd → Ruchy (Python-floor `%`)
$ cargo run --example 04_shell_roundtrip  -p xpile  # POSIX shell in → POSIX shell out
$ cargo run --example 05_python_to_shell  -p xpile  # `subprocess.run([...])` → shell
$ cargo run --example 06_inspect_session  -p xpile  # what's registered?

Each one prints input + output + a “what this demonstrates” block.

Next steps