From Python to Rust: Master Iterators by Rebuilding 10 Unix Tools
The fastest way I know to learn a language is to rebuild something you already understand. You stop fighting the problem and spend your attention on the syntax and the idioms. That is the whole idea behind the new Unix tools track I just released on the Rust platform: ten small command-line classics, each one a pure function you implement and cargo test to validate you got it right.
Why Unix tools, and why from Python
Every exercise opens with the Python you would write, then teaches the Rust idiom that replaces it. People who love the platform keep pointing at the same thing: the Python-to-Rust bridge is what makes the concepts stick.
You are not memorizing Iterator methods in the abstract, you are watching len(text.split()) turn into .split_whitespace().count(), and reaching for Option and Result instead of Python's exceptions.
Take cut. In Python an invalid field raises an exception at runtime:
def cut(text, delim, field): # field is 1-based
if field == 0:
raise ValueError("field values may not include zero")
...
In Rust both outcomes live in the return type:
#[derive(Debug, PartialEq)]
enum CutError {
ZeroField,
}
fn cut(text: &str, delim: char, field: usize) -> Result<Vec<&str>, CutError> {
// ...
}
#[test]
fn field_zero_is_an_error() {
assert_eq!(cut("a:b", ':', 0), Err(CutError::ZeroField));
}
Encoding the failure in the type, not just the docs, is a core Rust idea: the compiler makes every caller account for it, and that is a big part of what makes Rust code safer. The failing case becomes a test you code towards.
Iterators are the spine of the track, seven of the ten exercises turn for loops and comprehensions into iterator chains. Every exercise relates back to one or more Python idioms you already know.
For me Rust is hard but thanks to comparison with Python I feel that I understand more. - Piotr R
Having a direct comparison with Python snippets keeps me more in the context of what's going on. - Michal S
The track also follows one rule that matters for real CLIs: the logic lives in a pure, testable function, while the I/O (reading a file or stdin) stays in a thin wrapper around it.
That split is exactly how you would structure a professional Rust tool, and it is why every exercise can be validated by a test instead of by running a binary. I wrote more about how this rewiring changes your Python instincts in Rust made me a better Python developer.
The 10 exercises and what each one teaches
-
wc: count lines, words, characters. Iterators plus.count()replacelen(...), and you return a(usize, usize, usize)tuple. The character count is also a sneak intro to whychars().count()is notlen()in a Unicode world. -
head&tail: first and last N lines.headuses lazy.take(n)and stops early;tailforces you to collect first and slice from the end, because you cannot run an iterator backwards. -
cat -n: number the lines. Rust's.enumerate()starts at0, not1(there is nostart=argument), and you rebuild the numbered text from there. -
tr: translate and delete characters. The exercise that makes thecharvs&strdistinction click:'l'is achar,"l"is a&str, and you.map()over.chars()to rewrite each one. -
grep: filter matching lines, with-iand-v. Substring tests with.contains(), case folding, and a single boolean condition that handles both-iand-vwithout branching. First taste of borrowing and lifetimes in the signature.

-
cut: extract a field. Two outcomes that pull in different directions: a missing field is normal (skip the line), butfield == 0is a bad request. You model that withOptionandResultinstead of Python's exceptions. -
uniq -c: count adjacent duplicates. Rust has noitertools.groupby, so you walk runs by hand with pattern matching and aVec, keeping the consecutive-only behavior honest. -
sort: sort lines, with-n. Iterators do not sort, so youcollectinto amut Vecand call.sort(); numeric order meanssort_by_keywith a closure, Rust's answer to Python'skey=. -
sed s///: find and replace per line.str::replaceswaps every match,replacenstops after a count, so thegflag is just a choice between two methods, no manual counting. -
top_words: the capstone. Compose everything: count word frequencies with theHashMapentry API (entry().or_insert(), since there is noCounter), then sort and take the topn. This is thetr | sort | uniq -c | sort -rn | headpipeline rebuilt as one Rust function.
If you have been meaning to get past reading about Rust to actually writing it and making the concepts stick, begin with the free wc and head / tail exercises. Each one is short, has tests to code towards, and there is no AI on the platform; you have to do the work. I hope you learn a lot: start the Unix tools track.
Next up on the platform: a track on Rust lifetimes.
Learning Rust? I co-run a 6-week Python to Rust cohort where you build a performant JSON parser with PyO3 bindings.