Drop a codebase in. Get a Rust head-start out. Rustyfi turns Python / TypeScript / JavaScript / Go / C / C++ / Java / C# / Ruby projects into Rust crates. It treats the LLM as an unreliable generator and
cargo checkas the truth oracle β so the result is verified by the compiler, not vibes.
It is the difference between staring at a blank src/ and starting from a
compiling Rust project that already mirrors your structure, types, and logic.
Translating real software between languages is not a solved problem, and Rustyfi does not pretend otherwise. What you get depends on what you feed it:
| Your project | What comes out |
|---|---|
| Pure logic β CLIs, libraries, parsers, algorithms, data tools (standard library, no native deps) | Usually a clean cargo check β drop it, download it, it builds. See the calculator example. |
| Real-world apps β web services, framework-heavy, native-library-bound | A compiling skeleton with your modules wired up and the type-chaos eliminated, plus an honest NEXT_STEPS.md punch-list of what's left (framework API mappings, native deps). Run --deep to let the agentic doctor grind the remaining errors against cargo check. |
The UI never lies about which one you got. A partial run never masquerades as a
perfect one β the result banner and NEXT_STEPS.md tell you exactly where you
stand.
What no tool can do (including this one): take an arbitrary, framework-heavy application and emit clean, idiomatic Rust with zero human follow-up. Closing the last mile on a complex app means mapping one ecosystem's semantics onto another's (say, a Go web framework's request context onto Axum's extractors) β that needs a human or a much smarter model in the loop. Rustyfi gets you most of the way and is honest about the rest.
The examples/calculator project is a real lexer +
recursive-descent parser + CLI in Go. Run it through Rustyfi and the output crate
compiles clean and behaves identically:
# Go in:
2 + 3 * (4 - 1) 2 ^ 10 (1 + 2) * (3 + 4) 2 +
# Rust out (cargo check β 0 errors), same answers:
11 1024 21 error: unexpected token: end of inputTwo Go packages become src/main.rs and src/calc/mod.rs, Go's iota enum
becomes a real Rust enum, and Token/TokenKind keep one canonical shape
across files. Verified, not claimed.
Any OpenAI-compatible endpoint works. A fast, cheap default like DeepSeek is a great fit β bulk translation is voluminous and cheap; only the compile-fix loop benefits from a stronger model.
export RUSTYFI_LLM_API_KEY="your-api-key"
export RUSTYFI_LLM_BASE_URL="https://api.deepseek.com" # or OpenAI, Gemini, Cerebras, OpenRouterβ¦
export RUSTYFI_LLM_MODEL="deepseek-chat"
export RUSTYFI_NO_TIER=1 # non-OpenRouter providers: skip tier routing
# Optional but recommended β point the compile-fix loop at a reasoning model:
export RUSTYFI_FIX_MODEL="deepseek-reasoner"Multiple keys? Comma-separate them in RUSTYFI_LLM_API_KEY and Rustyfi
round-robins across them. Prefer xAI Grok via OAuth? export RUSTYFI_PROVIDER=grok.
Already have Claude Code? Use it β no API key, no extra spend. If the
claude CLI is installed and signed in,
Rustyfi can drive it as a model backend through your existing subscription.
This is the cheapest way to point the precision work (the compile-fix loop and
the --deep doctor) at a Claude-class model while keeping bulk translation on a
cheap API model:
# Cheap API model for the voluminous translation passβ¦
export RUSTYFI_LLM_API_KEY="your-api-key"
export RUSTYFI_LLM_BASE_URL="https://api.deepseek.com"
export RUSTYFI_LLM_MODEL="deepseek-chat"
# β¦and your local Claude Code (Opus) for the compile-fix loop + doctor:
export RUSTYFI_FIX_PROVIDER=claude_cli
export RUSTYFI_FIX_MODEL=opus
rustyfi ./myapp -o ./myapp-rust --deepSet RUSTYFI_PROVIDER=claude_cli to route everything (translation included)
through the CLI. The subprocess runs claude -p with ANTHROPIC_API_KEY
removed from its environment so it uses your Claude Code login rather than a
key; set RUSTYFI_CLAUDE_KEEP_KEY=1 to keep the key, or RUSTYFI_CLAUDE_BIN to
point at a non-default claude binary.
A β the CLI (no server, no browser; scriptable and CI-friendly):
# install the released binary (macOS Β· Linux Β· Windows, x86_64 + arm64):
curl -fsSL https://raw.githubusercontent.com/New1Direction/rustyfi/main/install.sh | sh
# β¦or from source: cargo install --git https://github.com/New1Direction/rustyfi rustyfi-cli
# β¦or in-tree: cargo run -p rustyfi-cli -- ./myapp -o ./myapp-rust
# then just point it at any project (a directory or a .zip):
rustyfi ./myapp -o ./myapp-rust
# stuck on the last few errors of a complex app? engage the agentic doctor:
rustyfi ./myapp -o ./myapp-rust --deep # needs a strong RUSTYFI_FIX_MODEL
# skip behavioral verification (e.g. source toolchain not present):
rustyfi ./myapp -o ./myapp-rust --no-behavior
# re-run behavioral checks against an already-built crate (edit/extend behavior.yaml, then):
rustyfi verify-behavior ./myapp-rust rustyfi πΊπ¦
source ./myapp
output ./myapp-rust (crate: myapp)
β Analyzing source
β Scaffolding + pinning type contract
β Translating to Rust ββββββββββββ 12/12
β Verifying with cargo check
Β· Auto-fixed 37 compile error(s) using the compiler's own suggestions (no AI needed)
β compiles clean
Β· 12 translated from go Β· 18 file(s) written Β· 0 todo!() stub(s)
β cd ./myapp-rust && cargo run
The exit code tells the truth, so it drops straight into a script or CI:
0 = compiles clean Β· 1 = compiles with errors (a head-start +
NEXT_STEPS.md) Β· 2 = failed. The crate path is printed to stdout;
everything else goes to stderr. Live progress is rich in a terminal and plain
when piped. Re-run the same command to resume where an interrupted run left
off (--fresh to start over).
B β the web UI (drag & drop, live stream):
cargo run -p rustyfi-server # β http://localhost:7410Open it, drag a ZIP onto the drop zone, hit Translate to Rust, and Download Rust Project when it's done. Same engine, same honesty β the terminal panel tells you whether your provider is configured before you upload.
The core idea: let the LLM generate, let cargo check judge, and do everything
the compiler can already tell you deterministically β for free.
Browser (drag & drop ZIP)
βΌ
rustyfi-server (Axum HTTP + SSE)
β zip-slip safe Β· content fingerprint (resume/reset) Β· bomb-capped
βΌ
rustyfi-engine::pipeline::run()
β
ββ [Analysis] walk Β· language detect Β· import edges
ββ [Scaffold] Cargo skeleton Β· directory-as-package module map
ββ [Contract] β one cheap call per package extracts the canonical Rust
β API (every struct's full fields, enums, traits, signatures),
β then COMPILER-VALIDATES it as a skeleton before fan-out β
β a structurally broken contract (e.g. a dyn-incompatible
β trait) is regenerated, never multiplied across every file
ββ [Translate] DAG-scheduled, semantically chunked, parallel, rate-gated;
β each file translated against the canonical contract
ββ [Verify] cargo check βββΊ the truth oracle
β β
β ββ rustfix β harvest rustc's OWN machine-applicable suggestions and
β β apply them deterministically (zero tokens). MaybeIncorrect
β β guesses are applied only if they reduce the error count.
β ββ dep repair strip hallucinated/unresolvable crates so resolution
β β succeeds and real errors become visible
β ββ fix loop targeted LLM repair per error family β with the trait
β β definitions, `rustc --explain`, and impls the error
β β names injected as context; deduped on every write
β ββ doctor β (--deep) an agentic loop that reads, searches, edits, and
β re-checks the crate until it compiles or the budget caps β
β src-confined and snapshot-reverted, so it can never make
β the crate worse than it found it; also engages on behavioral
β mismatches (keeps edits only if the crate still compiles AND
β mismatches strictly decrease, else reverts)
ββ [Package] ZIP output + NEXT_STEPS.md (Done is sent only after the ZIP
is on disk β the download can never race it)
cargo check tells you the translation compiles. Behavioral verification tells
you whether it runs the same way.
For CLI tools, a translation run auto-mines a fixture corpus from the project's
README and --help output, runs the source binary on each case to capture
golden stdout/stderr/exit-code, then β once the crate compiles β diffs the Rust
target against it. This is differential testing on a corpus of exercised
cases, not a proof of equivalence. It tells you: for every input in the
fixture set, the source and the Rust produce identical observable output. The
source binary is ground truth.
Non-deterministic cases are quarantined automatically: if running the source twice produces different output, that case is excluded from the corpus rather than silently letting it flap.
Two artifacts land in the output crate:
behavior.yamlβ the fixture corpus (build + run commands, per-stream compare mode, normalize rules, and per-case goldenexpect). Human-readable and hand-editable.behavior_report.jsonβ the diff of the last run: pass/fail per case, actual vs. expected output for every divergence.
behavior.yaml compare modes (per stream):
| Mode | Meaning |
|---|---|
exact |
byte-for-byte match |
ignore |
stream not checked |
normalized |
apply rules first (e.g. strip_trailing_ws, regex mask), then exact-match |
The review loop:
# After a translation, check the report:
cat ./myapp-rust/behavior_report.json
# Edit behavior.yaml (add cases, adjust compare modes, add normalize rules),
# then re-verify the already-built crate without re-translating:
rustyfi verify-behavior ./myapp-rust
# exit 0 = behaviors match Β· exit 1 = divergence--deep now also drives the doctor on behavioral mismatches β it will read,
edit, and re-verify the crate, keeping changes only when the crate still
compiles and the mismatch count strictly decreases.
Honest scope: behavioral verification auto-runs for CLI tools today.
Because it runs the source project, it is a local-only phase β the hosted web
flow never executes uploaded code. Skip it with --no-behavior.
Library behavioral verification is proven and rolling out. Most real targets
are libraries (no CLI to diff), so the engine synthesizes a thin driver that
exercises the public API in both the source language and Rust, then diffs the
output β turning a library into a comparable program. On itsdangerous the
translated Signer verified byte-identical to Python (sign, unsign, and
tamper-rejection), with both drivers model-generated. It ships today as a
verified engine capability; auto-wiring it into every library translation is
gated behind a flag while it hardens (it costs a model call per run). HTTP
service support is still planned.
Two ideas do most of the work, and both came from asking "what does the compiler already know that we're throwing away?"
- The contract phase fixes consistency where it's created. Translating each file in isolation makes the same type come out differently in different files (the classic "struct has 3 fields here, 2 fields there" bug). Pinning one canonical API per package up front eliminates that entire class of error β and in testing, the cheap model with the contract beat the expensive model without it.
- rustfix stops paying an LLM to re-derive fixes the compiler already
computed.
cargo checkemits structured, machine-applicable suggestions for a large class of errors; Rustyfi applies them directly. On a real run it fixed 200+ errors deterministically, before the model touched anything.
- Two oracles, not one β
cargo checkverifies compilation; behavioral verification diffs the source and Rust binaries on a fixture corpus. Both run before the result is declared. Neither is the model's word. - Honest output β if files fell back to stubs or the build isn't clean, the UI
and
NEXT_STEPS.mdsay so plainly. - Deterministic where possible β module wiring, dedup, dependency repair, and rustfix are exact, repeatable passes, not model guesses.
- Resumable by design β every phase checkpoints; identical re-uploads resume, changed re-uploads reset (content fingerprint).
- Fail fast on config β a missing/invalid API key aborts immediately with an actionable message instead of stubbing out every file.
- Safe by construction β edits are confined to the generated crate's
src/; a dependency's cached source is never touched.
| Variable | Default | Description |
|---|---|---|
RUSTYFI_LLM_API_KEY |
(required for openai) | API key(s) β comma-separate for round-robin |
RUSTYFI_LLM_BASE_URL |
https://openrouter.ai/api/v1 |
Any OpenAI-compatible base URL |
RUSTYFI_LLM_MODEL |
google/gemini-2.5-flash |
Translation model ID |
RUSTYFI_FIX_MODEL |
(falls back to RUSTYFI_LLM_MODEL) |
Stronger model for the compile-fix loop β translation is cheap, repair is precision work |
RUSTYFI_FIX_BASE_URL / RUSTYFI_FIX_API_KEY / RUSTYFI_FIX_PROVIDER |
(fall back to translation config) | Point the fix loop at a different endpoint entirely |
RUSTYFI_FIX_TIMEOUT |
180 |
Per-request timeout for fixes (reasoning models think longer) |
RUSTYFI_DEEP_FIX |
(unset) | Engage the agentic doctor on residual errors (the CLI's --deep sets this) |
RUSTYFI_DEEP_FIX_BUDGET |
40 |
Max doctor tool calls before it stops |
RUSTYFI_DEEP_FIX_TIMEOUT |
1200 |
Doctor wall-clock budget, seconds |
RUSTYFI_PROVIDER |
openai |
grok/xai for Grok OAuth, or claude_cli to drive the local Claude Code CLI (no API key) |
RUSTYFI_CLAUDE_BIN |
claude |
Path to the Claude Code binary when *_PROVIDER=claude_cli |
RUSTYFI_CLAUDE_KEEP_KEY |
(unset) | Keep ANTHROPIC_API_KEY for the claude subprocess instead of using its own login |
RUSTYFI_VERIFY_RETRIES |
4 |
Max compile-fix cycles |
RUSTYFI_RPM |
25 |
Global requests-per-minute gate across all workers |
RUSTYFI_PARALLEL |
16 |
Files translated concurrently |
RUSTYFI_CHUNK_TOKENS |
5000 |
Max tokens per semantic chunk |
RUSTYFI_NO_TIER |
(unset) | Disable tiered model routing (use for non-OpenRouter providers) |
RUSTYFI_NO_STUB |
(unset) | Translate test/fixture/generated files too |
PORT |
7410 |
HTTP server port |
RUST_LOG |
rustyfi_server=info |
Tracing filter |
crates/
βββ rustyfi-core/ # Typed state machine + cargo-check harness
β βββ state.rs Β· events.rs Β· transitions.rs # Orchestrator (exhaustive match)
β βββ context.rs Β· compiler.rs Β· errors.rs # manifest + diagnostic parsing
β
βββ rustyfi-engine/ # Pipeline orchestration + LLM + scaffolding
β βββ analysis.rs # walk, language detect, import resolution
β βββ checkpoint.rs # resumable per-phase checkpoints (JSON)
β βββ chunker.rs # SemanticChunker β token-budget file splitting
β βββ graph.rs # ModuleGraph β DAG + topological scheduler
β βββ scaffold.rs # Cargo generator, directory-as-package layout, ZIP
β βββ contract* # canonical per-package Rust API (in pipeline.rs/llm.rs)
β βββ contract_check.rs # β compiler-validate contracts before fan-out
β βββ slicer.rs # signature extraction / contract splitting
β βββ deps.rs # curated dependency auto-detection (allowlist-only)
β βββ rustfix.rs # apply rustc's own machine-applicable suggestions
β βββ fix_context.rs # β trait defs + rustc --explain + impls for fix prompts
β βββ agent_fix.rs # β the doctor β guarded, budgeted, snapshot-reverted
β βββ behavior/ # β behavioral verification β corpus mining, golden capture,
β β # diff runner, behavior.yaml + behavior_report.json writer
β βββ dedup_items.rs # syn-based duplicate-definition removal
β βββ llm.rs # blocking LLM client (OpenAI-compat + Grok OAuth)
β βββ pipeline.rs # end-to-end run() β phased, checkpointed, parallel
β
βββ rustyfi-server/ # Axum HTTP server β /health /api/translate (SSE) /api/download/:name
βββ rustyfi-cli/ # `rustyfi` command β drives the engine directly, no server
βββ main.rs # args, run, exit codes, summary
βββ progress.rs # TTY-aware live progress (rich interactive / plain piped)
βββ unzip.rs # zip-slip-safe archive in/out
web/ # Drop-zone UI: live SSE progress, honest result banner
examples/calculator/ # Go β clean Rust, verified
bench/ # Benchmark suite: pinned real repos, --json, scoreboard
cargo test --workspace
cargo clippy --workspace -- -D warningsThere are at least three. The trombone knows what it did. πΊ