I Built a Skill That Teaches AI Agents How to Actually Port C++ to Rust
TL;DR: I built an agent skill that teaches Claude, Gemini, and Codex how to systematically migrate C++ codebases to idiomatic Rust — not just translate syntax. It encodes lessons from real migrations at Google, Cloudflare, Meta, and Mozilla into a structured workflow. I tested it by porting asdcplib (a production C++ library used across the digital cinema industry) and got a working Rust implementation in a single pass. It’s on GitHub if you want to try it.
The Problem With “Port This to Rust”
If you’ve ever asked an AI agent to “port this C++ to Rust,” you know what happens. You get a mechanical, line-by-line translation that compiles but isn’t Rust. Class hierarchies become Arc<Mutex<T>> soup. Error handling stays stringly-typed. The borrow checker gets silenced with .clone() everywhere. The result technically works, but it’s worse than the C++ it replaced.
The core problem is that migrating C++ to Rust isn’t a translation problem — it’s a redesign problem. Rust’s ownership model, trait system, and enum-based dispatch require fundamentally different architectural thinking. No amount of pattern matching on syntax will produce idiomatic Rust from idiomatic C++.
I wanted to fix this. Instead of telling agents what syntax to use, the skill teaches them how to think about the migration — scope it incrementally, freeze behavior with tests first, redesign ownership rather than wrapping everything in smart pointers, and validate parity at every step.
What’s in the Skill
The skill is structured as a SKILL.md file (the canonical workflow) backed by 10 reference documents covering specific migration concerns:
cpp-to-rust-skill/
├── SKILL.md ← Core workflow (12 sections)
├── AGENTS.md / CLAUDE.md / GEMINI.md ← Cross-agent adapters
├── agents/openai.yaml ← Codex metadata
└── references/
├── porting-playbook.md ← Phase-by-phase migration
├── cpp-rust-pattern-map.md ← Basic construct translation
├── advanced-pattern-map.md ← Templates, RTTI, macros, move semantics
├── ffi-and-unsafe-boundaries.md ← FFI safety, ABI, panic handling
├── concurrency-porting.md ← Threads, async, atomics, Send/Sync
├── build-system-integration.md ← CMake, Bazel, Cargo integration
├── performance-traps.md ← Clone overhead, iterator bounds, enum bloat
├── perf-and-parity.md ← Differential testing, sanitizers
└── sources.md ← 40+ curated references
The workflow has a deliberate ordering that mirrors how successful large-scale migrations actually work: Scope → Freeze → Redesign → Port → Verify → Anti-Pattern Gate, iterating until the output is idiomatic.
Phase 0–1: Scope and Freeze
Before writing any Rust, you inventory the C++ codebase, identify the minimal required surface area, and freeze behavior with characterization tests and golden vectors. This is the step most people skip, and it’s the step that makes everything else work.
The skill enforces this: it tells the agent to build a parity harness with characterization tests, corpus/golden tests, benchmarks, and differential tests that run both implementations on the same inputs. Only then does porting begin.
Phase 2: Design Rust Architecture, Don’t Translate
The skill explicitly instructs agents to redesign rather than translate. Key rules include modeling invalid states as unrepresentable using enums and newtypes, replacing inheritance trees with traits and composition, replacing dynamic_cast/RTTI with enum dispatch, and replacing C++ preprocessor macros with const, generics, or cfg attributes.
This is where most AI-assisted ports go wrong. A naive agent will map a C++ class Animal with virtual methods to a Rust dyn Animal trait object. The skill teaches it to consider whether an enum with variants is more appropriate (it usually is for closed hierarchies).
Phase 3: Port in Vertical Slices
The skill mandates vertical slicing — port one subsystem end-to-end (data model, parser/logic, API boundary, tests) before moving to the next. No big-bang rewrites. Every slice must be releasable and benchmarked independently.
This principle comes directly from how Google, Cloudflare, and Meta executed their migrations. Every successful large-scale C++ to Rust migration used incremental replacement.
Concurrency Gets Special Treatment
C++ concurrency patterns don’t map cleanly to Rust. The skill has a dedicated reference document covering how to redesign around Send/Sync ownership rather than mimicking shared-state patterns, when to prefer channels over Arc<Mutex<T>>, mapping thread pools to Rayon or Crossbeam, mapping coroutines to async/await, and the critical rule of never holding a MutexGuard across an .await point.
FFI and unsafe Boundaries
For incremental migrations where C++ and Rust coexist, the skill covers FFI safety comprehensively: documenting invariants above every unsafe block, catching panics at FFI entry points (unwinding across FFI is undefined behavior), using #[repr(C)] on all shared types, and treating FFI as a transition seam rather than a permanent architecture.
Anti-Patterns Catalog
The skill includes an explicit “don’t do this” list that agents check against. These are patterns that appear in nearly every naive AI-generated Rust port:
- Wrapping everything in
Arc<Mutex<T>>to silence the borrow checker - Using
Rc<RefCell<T>>as a crutch (moving safety from compile-time to runtime panics) - Translating class hierarchies 1:1
- Cloning excessively instead of using borrows and
Cow
Cross-Agent Design
One design decision I’m particularly pleased with is the cross-agent adapter system. The skill works with Claude Code, Gemini CLI, and OpenAI Codex through lightweight adapter files that import shared instructions from AGENTS.md. The core operational knowledge lives in SKILL.md and references/, and each agent runtime gets a thin adapter that imports those shared rules plus any platform-specific notes.
This means the skill stays consistent regardless of which agent you use, while accommodating platform differences (like Claude preferring concise diffs vs. Gemini’s context handling).
Putting It to the Test: Porting asdcplib
I wanted to test this against something real, not a toy example. So I pointed it at asdcplib — CineCert’s C++ implementation of the AS-DCP file format, widely used across the digital cinema industry for packaging JPEG2000 video, PCM audio, and timed text into MXF containers. It’s a mature, non-trivial C++ codebase with deep ties to cryptographic operations, KLV parsing, and SMPTE standards.
Using the skill with Claude Code, I got a working Rust implementation in a single pass. Not perfect — it still needs optimization passes, thorough test coverage, and some manual refinement — but the core MXF reading logic compiled and ran correctly on the first attempt. That’s the difference between an agent that translates syntax and one that understands how to restructure a codebase around Rust’s ownership model.
The port is still private while I work through the remaining test coverage and performance tuning, but the fact that the skill produced a functional starting point from a real-world C++ library — not a textbook example — is what convinced me it was worth sharing.
What It Doesn’t Do
This skill is a workflow guide, not a transpiler. It won’t automatically convert your codebase. It teaches AI agents how to approach the migration systematically, but a human engineer still needs to drive the process, review the output, and make architectural decisions.
It also doesn’t cover every edge case. C++ codebases that are deeply entangled with platform-specific behavior (COM, Win32, ObjC++) will need additional guidance beyond what this skill provides.
Try It
The skill is on GitHub (MIT license). To use it with Claude Code:
# Clone into your project
git clone https://github.com/realadarsh/cpp-to-rust-skill
cp cpp-to-rust-skill/AGENTS.md cpp-to-rust-skill/CLAUDE.md .
# Then prompt Claude with:
# "Use the cpp-to-rust-port skill to migrate src/parser.cpp to Rust"
For Codex:
mkdir -p ~/.codex/skills/cpp-to-rust-port
cp -R cpp-to-rust-skill/* ~/.codex/skills/cpp-to-rust-port/