# rustnim — a Rust → Nim transpiler ## Status **Early scaffold.** `src/ty.rs` (type mapping) is written. `src/main.rs` is still cargo's default hello-world. Nothing transpiles yet. ## Why this exists We tried [tarekwasfy01/Code-Transpiler](https://github.com/tarekwasfy01/Code-Transpiler), which advertises `rust` as a source language, on the `base16ct` crate. It emits empty files and exits 0. The full investigation is in [`findings/`](findings/) and is published at https://rickub.com/nandi/code-transpiler-rust-frontend-findings The decisive finding, and the reason this is a new project rather than a patch: its Universal AST cannot represent Rust. `defaultSemanticTypeContract()` in `internal/backend/semantic_program.go:85` is hardcoded to ``` numeric: binary64, integer_width: unknown, truth: r_compatible, ownership: unknown, index_base: 1 ``` and `semantic_document.go:1014` *validates* that every contract equals exactly that, while `typed_operation.go:46` rejects any value model that is not `tagged_dynamic_binary64`. There is no integer width and no ownership in the model at all. Code like `base16ct`'s constant-time decoder — ```rust ret += (((0x2fi16 - byte) & (byte - 0x3a)) >> 8) & (byte - 47); ``` — depends on exact 16-bit signed wrapping and arithmetic shift. Lowering that into a 1-indexed dynamic float64 model produces silently wrong answers. So the first rule of this project is the one that codebase broke: > **Never approximate a semantic you cannot represent. Fail loudly instead.** `src/ty.rs` already does this: `i128`/`u128` are rejected with a reason rather than widened or truncated. ## Architecture ``` Rust source ──syn──> syn AST ──lower──> Nim source ──nim c──> binary ``` **The frontend is `syn`, deliberately.** Hand-rolling a Rust grammar is how the other project went wrong; a correct parser is not the interesting part of this problem. The interesting part is the lowering, which is where all the work goes. Planned modules: | file | role | state | |---|---|---| | `src/ty.rs` | Rust type → Nim type, exact widths, explicit rejections | written | | `src/lower.rs` | items, statements, expressions → Nim | todo | | `src/fmt.rs` | `println!`/`format!` format-string handling | todo | | `src/prelude.nim` | `Option`/`Result`/panic runtime, embedded in output | todo | | `src/main.rs` | CLI: `rustnim -o ` | todo | ## Mapping decisions made so far - **Integers**: exact width. `i32`→`int32`, `usize`→`uint`, etc. `i128`/`u128` rejected. - **Indexing**: both 0-based. Direct. - **`&T`** → plain value. **`&mut T`** → `var T` parameter. - **`&[T]`** → `openArray[T]` in parameter position, `seq[T]` when owned. `Nim::owned()` performs that conversion. - **Ownership/borrowck**: ignored. Nim is GC'd; for safe Rust this is sound. - **`Option`/`Result`** → object variants in the prelude. - **`match`** → Nim `case` where the arms are simple, `if`/`elif` when arms have guards or bindings. - **Rust's expression-orientation** maps well: Nim `if`/`case` are expressions too, and a proc's trailing expression is its return value. ### Open questions to settle empirically (the Nim toolchain is already here) 1. Is Nim's `shr` on a **signed** integer arithmetic or logical? `base16ct` needs arithmetic. Write the test before relying on either answer. 2. Rust debug builds **panic** on integer overflow; release builds wrap. Nim raises `OverflowDefect` by default. Decide which Rust profile we model, say so in the README, and map `wrapping_*`/`checked_*`/`saturating_*` explicitly. 3. `char`: Rust `char` is a Unicode scalar; mapped to `Rune`, which needs `std/unicode`. Confirm round-tripping. ## Testing: differential, not golden The bar is **behavioural equivalence with rustc**, not that the output looks plausible. For each case in `tests/cases/`: ``` rustc case.rs && ./case > expected rustnim case.rs -o case.nim && nim c -r case.nim > actual diff expected actual ``` A case only counts as passing when both binaries build *and* produce identical stdout. `tests/` currently has no runner — writing it is the next step, and it should come before any more of the lowering, so that progress is measured rather than asserted. ## Toolchain - `rustc` / `cargo` 1.98.1 — system. - Nim 2.2.4 — vendored at `.nim-toolchain/` (gitignored; downloaded from nim-lang.org, not installed system-wide). Binary: `.nim-toolchain/bin/nim`. ## Milestone 1 Transpile `base16ct` 1.0.0 — the crate the other transpiler failed on — and have its decoder produce byte-identical output to the Rust original. It is a good target: 613 lines, `no_std`, no dependencies, and its constant-time integer arithmetic is exactly the kind of thing a sloppy transpiler gets wrong.