AUGUST 2026 · MIGRATION RETROSPECTIVE

From Zig to Rust without stopping the roadmap

The project formerly known as μcharm became Kipferl while changing its implementation language, runtime host, release pipeline, and dependency strategy—without changing its promise: write a focused Python CLI and ship one fast native file.

1,669 / 1,669
compatibility checks
7.044 ms
median startup
3.98–4.77 MB
release runtimes
4 targets
native CI matrix

THE WHY

A foundation should reduce product risk

Zig helped μcharm prove that a small, embedded Python runtime could produce genuinely pleasant standalone CLI applications. But the project increasingly paid for a pre-1.0 language surface, changing build conventions, a smaller library ecosystem, and more custom systems code than the product needed to own.

The concern was not whether Zig could produce fast binaries. It could. The concern was the long-term cost of maintaining a CLI, loader, VM host, terminal UI, TLS, SQLite integration, archives, process management, and release tooling while the foundation kept moving. Ecosystem predictability and governance are engineering inputs too.

Rust offered a stable language and Cargo workflow, a larger pool of maintained libraries, explicit ownership at the PocketPy boundary, first-class test tooling, and mature cross-target release support. That aligned better with the real goal: spend maintenance effort on the developer experience, not on rebuilding infrastructure.

THE NAME

Why Kipferl?

The Rust release was also the right boundary for a clearer product name. A Kipferl is a crescent-shaped pastry, giving a small nod to Bun and matching the product metaphor: we bake a Python-style CLI into one portable executable. The repository, command, packages, and public home at kipferl.dev now use one memorable spelling.

The rename is intentionally compatible. Version 0.6 keeps the old command, import forms, environment variables, and release-asset aliases for a transition window. The MCHARM01 binary format stays frozen, so the new identity does not invalidate existing apps.

THE HOW

Replace one boundary at a time

01

Freeze the contract

We recorded startup, size, loader behavior, binary trailers, and compatibility before porting. Existing Zig artifacts became golden inputs instead of a vague reference implementation.

02

Prove the Rust spine

A Cargo workspace built the vendored PocketPy C runtime, owned the VM lifecycle, and crossed one narrow FFI boundary. Format and loader crates came next, preserving the existing on-disk format.

03

Port behavior in compatibility waves

The CLI and runtime modules moved in risk-ordered batches. Every batch ran the same Python under CPython and Kipferl, comparing status, stdout, and stderr. The suite expanded from the original 456-check baseline to 1,669 passing checks.

04

Use the ecosystem deliberately

We accepted Ratatui/Crossterm for interactive selection, Ureq/Rustls for maintained HTTPS, Rusqlite with bundled SQLite, and focused ZIP/TAR crates. We rejected options whose size, maturity, or dependency cost exceeded their maintenance value.

05

Cut over, release, then delete

Four-target CI built the runtime, loader, CLI, and a real standalone app. Only after `v0.6.0-rc.1` passed public-asset checksum and execution smokes did we remove the archived Zig tree—85,310 obsolete tracked lines in one recoverable Git commit.

THE OUTCOME

Better ownership, with explicit tradeoffs

MeasureResultWhat it means
Compatibility1,669 / 1,669All available checks pass; one host-unavailable TOML baseline remains outside the denominator.
Startup7.044 ms median, 7.980 ms p95Slower than the 4.332 ms legacy baseline after adding the maintained feature stack, but still inside the 10 ms goal.
Runtime size3.98–4.77 MB across RC targetsLarger than the 2.31 MB Zig ARM64 baseline; the budget now values correctness and maintainability over the smallest possible artifact.
Memory6.21 MB median empty-process RSSMeasured on Apple Silicon; the JSON workload used 15.56 MB.
Interactive selection9.100 ms median PTY round tripRatatui adds a responsive, tested viewport while preserving terminal state and shell scrollback.

The result we wanted

The rewrite did not win every microbenchmark, and we do not want to pretend it did. It produced a more stable foundation, removed bespoke protocol and archive code, made unsafe boundaries explicit, improved the interactive experience, and kept the product inside its startup and size budgets. That is a better long-term trade.

The next work is product work again: better stubs, cross-target reliability, tree-shaking, watch mode, and higher-level terminal experiences.

Reproduce the numbers