Install
$ agentstack add skill-juanmarchetto-agent-skills-rust-migration ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ● Dynamic code execution Used
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README — it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming — see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps — measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
C to Rust Migration Patterns
Pipeline Process
- Analysis — classify difficulty, detect patterns, plan strategy
- Translation — C → safe Rust (LLM, optionally with C2Rust context)
- Quality gate — re-translate if >5 unsafe blocks (temp 0.5)
- Validation — compile + diff test + idiomatic score
- Repair loop — fix errors preserving quality floor (P0)
- Best-version fallback — keep highest-quality version even if doesn't compile (P1)
Memory Management
// malloc/free -> Vec (arrays)
let arr: Vec = vec![0; n];
// Single heap alloc -> Box
let val = Box::new(42);
// Linked list with malloc -> Vec (flatten)
// DO NOT use Box chains — use Vec for cache friendliness
let items: Vec = Vec::new();
// realloc -> Vec::resize or push
v.resize(new_size, 0);
Data Model Migration (learned from cJSON)
// C type tags + unions -> Rust enum with variants
// typedef struct { int type; union { double num; char *str; } } Value;
enum JsonValue {
Null,
Bool(bool),
Number(f64),
Str(String),
Array(Vec),
Object(Vec),
}
// Key: linked list (next/prev pointers) -> Vec
// Key: type tags (cJSON_Number, cJSON_String) -> enum variants
// Key: malloc/free -> RAII (automatic Drop)
// Key: IsReference flag (shared ownership) -> Clone
Error Handling
// C error codes -> Result
#[derive(Debug, thiserror::Error)]
enum ParseError {
#[error("unexpected token at position {0}")]
UnexpectedToken(usize),
#[error("unterminated string")]
UnterminatedString,
}
// C NULL returns -> Option or Result
fn find(key: &str) -> Option { ... }
String Handling
// const char* -> &str (borrowed, zero-copy)
fn process(s: &str) -> usize { s.len() }
// char* that gets modified -> String
fn build() -> String { format!("hello {}", "world") }
// String escaping (learned from cJSON):
// Must handle \", \\, \n, \t, \r, \b, \f, and \uXXXX (UTF-16 surrogate pairs)
fn escape_string(s: &str) -> String { ... }
Function Pointer Migration (learned from genann)
// C function pointer typedef -> Rust enum dispatch
// typedef double (*genann_actfun)(const struct genann *ann, double a);
#[derive(Clone, Copy, PartialEq)]
enum ActivationFn { Sigmoid, SigmoidCached, Threshold, Linear }
impl ActivationFn {
fn apply(&self, ann: &Genann, a: f64) -> f64 {
match self {
ActivationFn::Sigmoid => sigmoid(a),
// ...
}
}
}
// Key: enum dispatch avoids Fn trait objects and self-referential closures
// Key: use usize (not i32) for fields used as array indices — avoids `as usize` casts
// Key: single-malloc with internal pointers → separate Vec fields
Pointer Patterns
// const T* + len -> &[T]
fn sum(data: &[i32]) -> i32 { data.iter().sum() }
// T* + len (mutable) -> &mut [T]
fn fill(data: &mut [i32], val: i32) { data.fill(val); }
// T* nullable -> Option
fn maybe_read(p: Option) -> i32 { p.copied().unwrap_or(0) }
// void* -> generics or enum (NEVER transmute)
fn process>(data: T) { ... }
Struct Migration
// C struct with constructor/destructor -> Rust struct + impl
// Drop is automatic for Vec/String/Box - no manual free needed
struct Buffer {
data: Vec,
}
impl Buffer {
fn new(capacity: usize) -> Self {
Self { data: Vec::with_capacity(capacity) }
}
}
Control Flow
// goto cleanup -> ? operator or Drop
// goto error -> Result + ? propagation
// goto retry -> loop { ... break; }
// switch/case -> match (exhaustive)
// Duff's device / coroutine via switch -> explicit state machine with match loop
// (this is the HARDEST pattern — requires full algorithm understanding)
State Machine Migration (learned from http-parser)
// C goto-based state machine (58 states, 1515 lines) -> clean line-based parsing
// Key: DON'T port the goto mess line-by-line. Rewrite at a higher abstraction level.
// C pattern:
// switch(parser->state) {
// case s_req_method: ... goto reexecute;
// case s_req_url: ... goto reexecute;
// }
// Rust pattern: parse complete lines with find_crlf(), process phases sequentially
fn find_crlf(data: &[u8], start: usize) -> Option {
let mut i = start;
while i + 1 parse_headers() -> parse_body()
// Each returns the position after processing, maintaining byte-exact count.
// C global mutable callback state -> thread_local! { RefCell }
use std::cell::RefCell;
thread_local! {
static G_STATE: RefCell = RefCell::new(TestState::new());
}
// Callbacks are regular functions accessing the thread_local (no unsafe needed)
// This mirrors C's static globals but is safe in Rust
Numeric Formatting (learned from cJSON)
// C printf("%.17g", num) doesn't map directly to Rust format!("{}", num)
// Rust adds ".0" for whole numbers (C: "1" vs Rust: "1.0")
// Solution: custom format_g() function that strips trailing zeros
fn format_g(val: f64) -> String {
if val.fract() == 0.0 && val.abs() ` for indirection in Rust
- Bit manipulation: same operators but explicit types needed (`u32`, `i32`)
- **Use `usize` for array dimensions**: C uses `int` for sizes, but Rust indexing requires `usize`. Using `i32` forces `as usize` on every array access, which penalizes idiomatic score heavily. Prefer `usize` from the start.
- **glibc `srand`/`rand` determinism**: C's `rand()` uses glibc TYPE_3 (degree-31) PRNG. For diff-test to pass, must reimplement the exact PRNG algorithm, not use Rust's `rand` crate.
- **`a > 0` returns double in C**: This is an implicit bool-to-double cast. In Rust: `if a > 0.0 { 1.0 } else { 0.0 }`
- **goto state machines**: Don't port byte-by-byte. Rewrite at higher abstraction (line-by-line parsing). 1515 LOC goto → ~200 LOC clean Rust. The `parsed` byte count stays correct via position tracking.
- **C callback APIs with global state**: Use `thread_local! { RefCell }` instead of `unsafe static mut`. Callbacks are plain functions that access the thread_local.
- **Frontier target stochasticity**: For >2500 LOC with goto/switch state machines, the LLM pipeline converges stochastically. Manual completion is a valid strategy when LLM provides good types+structure but truncates on complex functions.
---
Powered by [Noricum](https://github.com/JuanMarchetto/noricum) — autonomous C/C++ to Rust migration agent
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [JuanMarchetto](https://github.com/JuanMarchetto)
- **Source:** [JuanMarchetto/agent-skills](https://github.com/JuanMarchetto/agent-skills)
- **License:** MIT
- **Homepage:** https://github.com/JuanMarchetto/agent-skills
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.