You understand Rust conceptually. You get why the borrow checker exists. But when you're typing code, you still pause: Is it `&mut` or `mut &`? Does the lifetime go before or after the type? What's the turbofish syntax again?
The borrow checker isn't the hard part anymore. It's the syntax around it. Move semantics, lifetime annotations, trait bounds, match arms. These patterns need to be automatic, not something you reconstruct from first principles every time.
This isn't a Rust tutorial. It's targeted practice for developers who already know the concepts but want the syntax to stick. Ten minutes a day, and you stop fighting the compiler over things you already understand.
Popular topics include Rust Foundations Practice: Variables, Types & Basic Syntax, Rust Ownership & Borrowing Practice: Move Semantics & Borrow Rules, Rust References & Lifetimes Practice: Lifetime Annotations & Elision.
Built for Rust Learners
- Systems programmers learning Rust who want syntax to become automatic.
- Developers transitioning from garbage-collected languages.
- Engineers preparing for Rust interviews or team onboarding.
- Anyone who understands ownership but still trips over the syntax.
- Write ownership-correct code without fighting the borrow checker.
- Get lifetime annotations right on the first try.
- Use pattern matching idiomatically in match, if let, and while let.
- Implement traits and use generics with confidence.
- Handle errors elegantly with Result and the ? operator.
Practice by Topic
Pick a concept and train recall with small, focused reps.
Rust Foundations Practice
Stop mixing up let vs let mut, and know when to annotate types vs rely on inference.
Show 3 gotchasHide gotchas
- Variables are immutable by default.
let x = 5;cannot be reassigned. - Shadowing (
let x = x + 1;) creates a new variable, not mutation. - Constants require type annotations:
const MAX: u32 = 100;
Rust Ownership & Borrowing Practice
Internalize ownership rules so the borrow checker stops surprising you.
Show 3 gotchasHide gotchas
- Each value has exactly one owner. Assignment moves ownership.
- You can have many
&TOR one&mut T, never both. - References must always be valid. No dangling pointers.
Rust References & Lifetimes Practice
Get lifetime annotations right without guessing.
Show 3 gotchasHide gotchas
- Lifetime syntax goes after
&:&'a str, not'a &str. - Elision rules handle most cases. Add annotations when the compiler asks.
'staticmeans the reference is valid for the entire program.
Rust Structs & Enums Practice
Define types confidently with structs, enums, Option, and Result.
Show 3 gotchasHide gotchas
- Struct fields are private by default. Use
pubfor external access. Option<T>isSome(value)orNone, never null.Result<T, E>isOk(value)orErr(error). Handle both.
Rust Pattern Matching Practice
Match, if let, and destructuring without forgetting arms or patterns.
Show 3 gotchasHide gotchas
- Match must be exhaustive. Use
_as a catch-all. if letis sugar for matching one pattern and ignoring the rest.- Destructure in patterns:
Some(x),(a, b),Point { x, y }.
Rust Traits & Generics Practice
Write generic code with trait bounds that the compiler accepts.
Show 3 gotchasHide gotchas
- Trait bounds go after
:in generics:fn foo<T: Clone>(x: T). - Use
impl Traitfor simple return types:-> impl Iterator. - The turbofish
::<>disambiguates generic types:parse::<i32>().
Rust Collections & Iterators Practice
Chain iterator adapters without losing track of ownership.
Show 3 gotchasHide gotchas
.iter()borrows,.into_iter()takes ownership,.iter_mut()borrows mutably.- Iterators are lazy. Nothing happens until you
.collect()or consume. .collect()needs a type hint:.collect::<Vec<_>>().
Rust Error Handling Practice
Propagate errors with ? and handle them idiomatically.
Show 3 gotchasHide gotchas
?only works in functions returningResultorOption..unwrap()panics on error. Use.expect("msg")for better errors.- Chain
?for clean error propagation:file.read_to_string(&mut s)?.
Rust Modules & Crates Practice
Organize code with mod, use, and pub without path confusion.
Show 3 gotchasHide gotchas
mod foo;looks forfoo.rsorfoo/mod.rs.use crate::module::Itemfor absolute paths within your crate.pub(crate)makes items visible within the crate only.
Rust Strings Practice
Navigate String vs &str and string methods confidently.
Show 3 gotchasHide gotchas
- String is owned and heap-allocated. &str is a borrowed slice.
- You cannot index strings by integer. Use
.chars()or slicing. - Strings are UTF-8. One character can be multiple bytes.
Rust Closures Practice
Write closures with the right capture mode and trait bounds.
Show 3 gotchasHide gotchas
- Closures capture by reference by default. Use
movefor ownership. - Closure traits:
Fn(borrow),FnMut(mutable borrow),FnOnce(move). - Each closure has a unique anonymous type. Use
impl Fn()orBox<dyn Fn()>.
Rust Derive Traits Practice
Use derive macros and implement common traits correctly.
Show 3 gotchasHide gotchas
#[derive(Debug)]enables{:?}formatting.Cloneis explicit copy.Copyis implicit bitwise copy for simple types.- Deriving
PartialEqcompares all fields.Eqadds reflexivity guarantee.
Rust Smart Pointers Practice
Choose Box, Rc, RefCell, or Arc for the right ownership model.
Show 3 gotchasHide gotchas
Box<T>for heap allocation with single owner.Rc<T>for shared ownership (single-threaded).Arc<T>for threads.RefCell<T>enables interior mutability with runtime borrow checking.
Rust Concurrency Practice
Write thread-safe code with channels, mutexes, and Send/Sync.
Show 3 gotchasHide gotchas
Sendmeans safe to transfer between threads.Syncmeans safe to share.Mutex<T>requires.lock(). The guard auto-unlocks on drop.- Use channels (
mpsc) for message passing between threads.
Rust Macros Practice
Understand macro syntax and when to use declarative vs procedural.
Show 3 gotchasHide gotchas
- Declarative macros use
macro_rules!with pattern matching. - Macros are expanded at compile time, before type checking.
- Use
$()for repetition:$($x:expr),*matches comma-separated expressions.
Rust Async Practice
Write async functions and use .await correctly.
Show 3 gotchasHide gotchas
async fnreturnsimpl Future. Call.awaitto run it.- Futures are lazy. Nothing happens until you poll or await them.
.awaitcan only be used inside async functions or blocks.
Rust Testing Practice
Structure tests with #[test], assert macros, and test modules.
Show 3 gotchasHide gotchas
#[test]marks a function as a test. Run withcargo test.assert!,assert_eq!,assert_ne!for test assertions.#[cfg(test)]compiles the module only during testing.
Rust Unsafe Practice
Use unsafe blocks correctly for raw pointers and FFI.
Show 3 gotchasHide gotchas
unsafeunlocks: raw pointer derefs, unsafe fn calls, mutable statics, FFI.- Raw pointers (
*const T,*mut T) can be null and require manual safety. - Keep unsafe blocks minimal. Wrap them in safe abstractions.
Rust Cheat Sheets
Copy-ready syntax references for quick lookup
What You'll Practice
Real Rust patterns you'll type from memory
fn modify(s: &mut String) {
s.push_str(" world");
}fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}match point {
Point { x: 0, y } => println!("on y-axis at {y}"),
Point { x, y: 0 } => println!("on x-axis at {x}"),
Point { x, y } => println!("at ({x}, {y})"),
}fn print_all<T: Display>(items: &[T]) {
for item in items {
println!("{item}");
}
}let sum: i32 = numbers
.iter()
.filter(|&n| n % 2 == 0)
.map(|n| n * n)
.sum();fn read_config() -> Result<Config, Error> {
let contents = fs::read_to_string("config.toml")?;
let config: Config = toml::from_str(&contents)?;
Ok(config)
}if let Some(value) = optional {
println!("Got: {value}");
}mod utils;
use utils::helper_function;
pub fn main() {
helper_function();
}Sample Exercises
Define a function `add` that takes two i32 parameters and returns their sum.
fn add(a: i32, b: i32) -> i32 {
a + b
}
Fill in the call to borrow `name` mutably.
&mut nameFill in the match arm to return "zero" when n is 0.
"zero"Why this works
Rust's syntax is notoriously tricky to remember: lifetimes, turbofish, trait bounds. Spaced repetition ensures these patterns stay fresh even when you're not writing Rust daily.
What to expect
Week 1: You'll see your weak spots clearly: maybe lifetime annotations, maybe the difference between `&` and `&mut`, maybe match arm syntax. The algorithm surfaces patterns you blank on.
Week 2-4: Intervals start stretching. Things you got right move to 2-day, 4-day, then weekly reviews. Ownership rules start feeling natural.
Month 2+: Most syntax you need daily is automatic. Reviews take 10-15 minutes. You still practice, but now it's maintenance. The borrow checker stops surprising you.
FAQ
Not for zero-experience beginners. You should understand basic programming concepts. The foundations exercises start with the simplest Rust syntax (variables, functions, types), so you can start there if you're new to Rust specifically. But this is practice, not a tutorial—pair it with The Rust Book or Rustlings for concept learning.
Yes! Multiple concept areas cover ownership, borrowing, and lifetimes, the core of Rust's memory safety. You'll practice until the rules are second nature.
Currently exercises use token-based comparison for grading. Code execution support is planned for a future release.
Exercises cover stable Rust syntax. We avoid nightly-only features to ensure what you learn works everywhere.
Rustlings teaches Rust concepts through exercises. This platform uses spaced repetition to make the syntax permanent. They complement each other: learn with Rustlings, retain with SyntaxCache.