Rust typing practice: a method, not a speed test
Generic typing tests measure prose. Prose has no &mut self, no Result<T, E> and no =>. Getting faster at English does almost nothing for the sequences that actually slow you down in a Rust file — so this page is about drilling those sequences specifically.
Why Rust is structurally slow to type
Three properties compound. None is unique to Rust, but few languages have all three at once.
Symbol density. Ordinary Rust runs to a much higher share of shifted and punctuation characters than ordinary prose, and shifted characters are slower and less accurate for almost everyone. Angle brackets, ampersands, pipes for closures, question marks and fat arrows all sit on the shifted layer.
Prefix operators. &, * and ? attach to what follows rather than sitting between two operands. A prefix symbol has to be decided before the word starts, so the decision lands in the middle of a motion that is already underway.
Nested closers. Rc<RefCell<Vec<T>>> ends in three closing angle brackets. Your editor probably inserts them; your fingers still have to decide how many to skip past, and that decision is a pause.
What to measure instead of WPM
Words per minute is the wrong headline number for code. It rewards long identifiers and punishes dense expressions, so a slow, thoughtful line of generics scores worse than a fast line of comments. These four are more useful.
| Token | Reads as | Why it is awkward |
|---|---|---|
Accuracy | correct keystrokes / all keystrokes | Counts every attempt. Mistyping the same token five times costs five, not one — position-based accuracy hides exactly the flailing you want to see. |
Consistency | evenness of your rhythm | Bursts separated by stalls score low even at a high average speed. It is the clearest signal that syntax is still in your head rather than your hands. |
Weak tokens | what you got wrong | Tokens containing at least one mistyped character, counted per occurrence rather than per keypress. |
Hesitation | what you got right, slowly | Median pause before a token versus your own baseline. Invisible to every mistake-based report, and usually the more actionable list. |
The last one is the reason this tool exists. A developer who types &mut self correctly every time but pauses 480ms before it has not learned &mut self. They have learned to look it up quickly.
The order that works
Practising everything at once means practising nothing well. This order moves from shapes that appear in every file to shapes that only make sense once the earlier ones are automatic.
- Rust basics —
struct,impl, your firstenum. - Enum and match — until
::and=>need no thought. - Option and Result — until
Some,None,OkandErrare reflexes. - Borrowing — until
&and&mutstop interrupting the sentence. - Iterator chains —
.iter().map().filter().collect()as one motion. - Traits and generics — bounds,
whereclauses,dyndispatch. - Error handling —
?, custom error enums,Fromconversions. - Lifetimes, closures, smart pointers — the annotations that read as noise until they do not.
- DSA in Rust — only once syntax has stopped being the bottleneck.
- Advanced DSA — dynamic programming, heaps, graphs,
Option<Box<T>>.
Steps one to four are the ones that pay. If you stop there, you will still have removed most of the friction from everyday Rust.
Copy, gap, recall
Three modes, in increasing order of difficulty. The text you type is identical in all three — only how much of it stays visible changes — so your scores remain comparable as you move up.
fn describe(value: Option<i32>) -> &'static str {
match value {
Some(0) => "zero",
Some(_) => "some",
None => "none",
}
}fn describe(value: ······· -> &'static str {
····· value {
····· => "zero",
Some(_) ·· "some",
···· => "none",
}
}Recall mode goes further: only the signature line stays and the whole body is blanked. Moving a snippet from copy to recall is the real measure of progress here. Adjusted WPM is not.
How the token analysis avoids false positives
A trainer that counts tokens by substring search will tell you that you struggle with match because you mistyped a comment containing the word “rematch”. SyntaxGym lexes the source into code, comment and string regions before counting anything, and requires identifier boundaries.
// rematch this later <- inside a comment
let label = "match arm"; // <- inside a string literal
fn rematch() {} // <- inside a longer identifier
fn longest<'a>(x: &'a str) // <- 'a is a lifetime, not a char literalThat last one matters more than it looks. Treat 'a as an opening character literal and everything after it on the line becomes string content, so the whole signature silently disappears from analysis. The same rules are implemented twice — once in TypeScript for the browser, once in Rust as a reference implementation — and both are covered by tests.
What this will not do
It will not teach you Rust. It has no compiler, does not typecheck, and cannot tell you whether the code you just typed is correct beyond character-by-character comparison. It will not make you a faster programmer in any general sense, because typing has never been the bottleneck in software.
What it does is narrower: it removes the small, repeated interruptions where your hands stop to work out what character comes next. That is worth removing, and it is measurable. Everything else on this site is in service of measuring it honestly.
Frequently asked questions
- Is typing practice actually useful for programmers?
- For general speed, no — thinking dominates, not typing. For specific unfamiliar sequences, yes. The value is not typing faster overall; it is removing the sub-second stalls where your hands stop to work out whether it is &mut self or &self, or how many closing angle brackets are pending. Those stalls interrupt your train of thought, and that is the real cost.
- How long does it take to make Rust syntax automatic?
- It varies, and anyone quoting a number is guessing. The measurable signal is your own hesitation report: when a token's median latency drops to your session baseline and stays there across several sessions, it is automatic. Tracking that per token is more useful than tracking days.
- Why not just use a normal typing test?
- Generic typing tests use prose. Prose is lowercase letters, spaces and the occasional comma — almost none of the shifted symbols, prefix operators and nested brackets that make code slow. Practising English words builds the wrong motor patterns for &mut, ?; and Rc<RefCell<T>>.
- Does SyntaxGym store my typing data anywhere?
- No. Sessions, custom snippets and settings are written to your browser's localStorage and never leave the machine. There is no account, no server-side database and no analytics on your keystrokes. Clearing your browser data deletes everything, which is also why the app offers an export.
Keep reading
- Enum and match practice
Step two of the order: the fat arrow and exhaustive matching.
- Option and Result practice
Step three: the two types that generate more keystrokes than anything else.
- Borrowing and ownership practice
Step four: why the ampersand breaks your rhythm and how to fix it.
- Collections and DSA practice
Steps nine and ten: algorithms, once syntax has stopped being the bottleneck.
Try it on a real snippet
No account, nothing uploaded. Your results stay in this browser.