I ported a small CLI tool from Python to Rust over a long weekend using aider. The tool: a log file analyzer with various filtering and aggregation features. About 2000 lines of Python becoming about 2400 lines of Rust.
Cross-language ports are an interesting AI tool case. Some parts translate mechanically; others require idiomatic judgment.
Why port?
The Python version was slow on large log files. Profiling showed Python’s overhead dominated; the actual logic was simple. Rust would be ~10x faster for this workload.
Two-day estimated effort manually. With aider, four-day actual effort with much better quality than I’d have produced manually.
What I did first
Before opening aider:
- Read the Python codebase fully
- Decided on the Rust crates to use (clap, regex, csv, serde)
- Sketched the module structure
- Wrote one Rust module by hand to establish patterns
The hand-written module was the reference for aider. It showed the conventions I wanted: error handling, struct organization, function patterns.
The translation flow
For each Python module:
> /add src/python_orig/parser.py
> /add src/rust/lib.rs (the reference I wrote)
> /add src/rust/Cargo.toml
>
> Translate parser.py to Rust. Keep the same public functionality.
> Use the patterns from lib.rs (error handling, struct style, etc.).
> Place the new code in src/rust/parser.rs.
Aider produced a Rust translation. I reviewed for:
- Same behavior (does it do what the Python did?)
- Idiomatic Rust (does it look like Rust, not Python written in Rust syntax?)
- Performance (any obvious inefficiencies?)
- Error handling (using Result instead of exceptions correctly?)
Average time per module: 45-60 minutes including review and refinement.
What translated cleanly
Pure functions. Functions that took inputs and returned outputs translated cleanly. Aider mapped Python types to Rust types reasonably.
Data structures. Python dicts became HashMap; Python lists became Vec; Python tuples became tuple structs. Mostly mechanical.
Iteration patterns. Python’s for x in items became Rust’s for x in items.iter(). Filter/map operations became Iterator methods. Idiomatic Rust came out.
Argparse to clap. Aider knew the clap patterns; the CLI definition translated cleanly.
File I/O. Python’s open() became Rust’s File::open with proper error handling. Aider added ? operators correctly.
What didn’t translate cleanly
Exception handling. Python uses exceptions liberally; Rust uses Result. Aider’s first attempts often had wrong error types or unclear error chains. Manual cleanup.
Mutable state in loops. Python patterns like accumulating into a list inside a loop translate to Rust but the idiomatic version is often different (using fold or collect). Aider sometimes produced literal translations rather than idiomatic ones.
Module structure. Python’s import and Rust’s use work differently. Aider’s first attempts at module organization didn’t always match Rust conventions.
Lifetime annotations. When the Python code passed references around freely, the Rust translation needed lifetime annotations. Aider sometimes got these wrong.
Mutability. Python mutates freely; Rust requires &mut. Aider’s translations sometimes kept things mutable that didn’t need to be (and vice versa).
A specific tricky bit
The original Python had a class:
class LogAnalyzer:
def __init__(self, config):
self.config = config
self.stats = {}
def analyze(self, lines):
for line in lines:
... # mutates self.stats
return self.stats
A direct Rust translation would be:
struct LogAnalyzer {
config: Config,
stats: HashMap<String, u64>,
}
impl LogAnalyzer {
fn analyze(&mut self, lines: impl Iterator<Item = String>) -> &HashMap<String, u64> {
for line in lines {
// mutates self.stats
}
&self.stats
}
}
This works but isn’t idiomatic Rust. The idiomatic version separates the data flow:
fn analyze(config: &Config, lines: impl Iterator<Item = String>) -> HashMap<String, u64> {
lines.fold(HashMap::new(), |mut stats, line| {
// ... compute updates to stats ...
stats
})
}
No struct, no mutability. Just functions and immutable values.
Aider’s first attempt was the direct translation. I rewrote to the idiomatic version. The lesson: AI tools translate; idiomatic Rust requires human review.
Productivity numbers
- Lines of Python: ~2000
- Lines of Rust: ~2400 (Rust is more verbose for some patterns)
- Time: 4 days of focused work
- Aider API spend: $32
- Performance improvement: 18x faster on a 1GB log file
The performance improvement was the goal. The port achieved it.
What I’d recommend for similar ports
For cross-language ports with AI tools:
Write the reference module by hand. This sets the patterns aider follows.
Pin reference modules in chat. Don’t let aider drift from your conventions.
Translate piece by piece. Don’t ask aider to translate the whole codebase at once. Module-by-module is better.
Plan for idiom review. AI tools translate; they don’t write idiomatic code consistently. Budget time for idiomatic refinement.
Use the type system aggressively. Strong typing catches translation errors that mismatch types.
Test parity. Compare outputs of Python and Rust versions on representative inputs. Catch behavioral differences early.
Don’t expect perfection. Cross-language ports always have some manual rework. AI helps with the bulk; you do the polish.
When ports are AI-suitable
Some ports are easier than others:
Stateless or mostly-pure code: Translates well. Pure functions, simple data flows.
Code with rich type information: TypeScript to other typed languages, for example. AI uses the types as a guide.
Algorithmically-defined code: When the code expresses a clear algorithm, translation works well.
Some ports are harder:
Heavily idiomatic code: Code that uses language-specific idioms heavily. Translation produces unidiomatic results.
Stateful code with complex lifecycles: OOP-heavy Python to Rust is harder than functional Python to Rust.
Code relying on language-specific libraries: Libraries that don’t have direct equivalents require finding alternatives.
For the second category, manual port may be faster than AI-assisted port with extensive cleanup.
Worth the AI investment
For my CLI tool port, AI tools were definitely worth it. The 4 days vs my estimate of 2 days isn’t a savings, but the quality of the port is better than I’d have produced rushed in 2 days.
The Rust code is solid. The performance is what I wanted. The codebase will be maintainable.
For someone considering a similar port: AI tools make it more accessible. The skills needed are still real (Rust knowledge), but the typing-heavy parts are accelerated. Worth trying.
A note on what I learned
The port taught me Rust patterns I didn’t know I needed. Watching aider’s translations and refining them was a learning exercise in itself.
A pattern: AI tools as learning aids. Even when their output isn’t immediately right, watching and refining their suggestions teaches you the right patterns. The end product is a port; the side effect is improved Rust skills.
This is one of the underrated AI tool benefits: passive skill improvement through corrective interaction. Worth noticing.