🦀 Rust Learning Roadmap — From Zero to Production
⚡ Note for Learners
This roadmap is not the official Rust documentation.
It is a structured guide to help beginners and self-learners approach Rust step by step, without feeling overwhelmed.
✨ Each section gives you:
- 📖 What → A short introduction of the concept
- 💡 Why → Why this concept is important in Rust
- 🛠️ How → Step-by-step explanation with code examples
- 📝 Exercises → Small tasks for practice
- 🔗 Links → Official documentation & further reading
📚 Recommended official resources:
🌱 Remember: Learning Rust takes time, and that’s okay!
Don’t rush — enjoy the journey, build small projects, and you’ll grow stronger every step.
💪 Good luck & happy coding! You’ve got this! 🚀
0. Setup on Windows 11 + WSL + VS Code
What: A productive Rust environment using Ubuntu on WSL, rustup, and VS Code.
Why: Native‑like Linux toolchain and fast builds on Windows.
Steps
- Enable WSL (PowerShell as Admin):
wsl --install
Restart if asked. Launch Ubuntu from Start Menu.
- Update Ubuntu packages:
sudo apt update && sudo apt upgrade -y
- Install Rust (rustup):
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
rustc --version
cargo --version
- Install VS Code & Extensions (Windows side):
- VS Code: https://code.visualstudio.com/
- Extensions: Remote – WSL, Rust Analyzer
- Create & run your first project:
cargo new hello-rust
cd hello-rust
cargo run
Further Reading:
- Rustup: https://rustup.rs/
- The Cargo Book: https://doc.rust-lang.org/cargo/
- Rust Analyzer: https://rust-analyzer.github.io/
1. Rust Basics
1.1 Variables, Constants, Mutability
What: Named values in memory. Variables are immutable by default; use mut for mutability. const is always immutable and needs a type.
Why: Immutability prevents accidental changes; constants document invariants.
How & Code:
fn main() {
let x = 5; // immutable
let mut y = 10; // mutable
y += 1;
const PI: f64 = 3.14159; // constant with explicit type
println!("x={x}, y={y}, pi={PI}");
}
Exercises:
- Create
countstarting at 0, increment to 5 in a loop and print each step. - Shadow a variable name to change its type (e.g.,
let s = "42"; let s: i32 = s.parse().unwrap();).
Further Reading:
- The Book – Variables & Mutability: https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html
- Rust by Example – Variables: https://doc.rust-lang.org/rust-by-example/variable.html
1.2 Functions
What: Reusable blocks of code with inputs/outputs.
Why: Organization, reuse, testability.
How & Code:
fn add(a: i32, b: i32) -> i32 {
a + b // expression (no semicolon = return)
}
fn main() {
let sum = add(2, 3);
println!("sum = {sum}");
}
Exercises:
- Write
area(w: u32, h: u32) -> u32returning rectangle area. - Write
greet(name: &str) -> Stringthat returns"Hello, !".
Further Reading:
- The Book – Functions: https://doc.rust-lang.org/book/ch03-03-how-functions-work.html
- Rust by Example – Functions: https://doc.rust-lang.org/rust-by-example/fn.html
1.3 Data Types (Integer, Float, String, Boolean)
What: Primitive & standard types: i32, u64, f32, f64, bool, char, &str, String.
Why: Correctness and performance depend on proper types.
How & Code:
fn main() {
let a: i32 = -10;
let b: u64 = 42;
let c: f64 = 3.14;
let t: bool = true;
let ch: char = '🦀';
let s: &str = "hello"; // string slice
let mut owned: String = s.to_string(); // heap-allocated String
owned.push('!');
println!("{a} {b} {c} {t} {ch} {owned}");
}
Exercises:
- Convert an
&strtoString, append text, and print length. - Safely parse
"123"toi32and handle error if it fails.
Further Reading:
- The Book – Data Types: https://doc.rust-lang.org/book/ch03-02-data-types.html
- Rust by Example – Primitives: https://doc.rust-lang.org/rust-by-example/primitives.html
1.4 Control Flow (if, match, loop, while, for)
What: Branching and iteration.
Why: Express logic clearly and safely.
How & Code:
fn main() {
let n = 7;
// if / else
let kind = if n % 2 == 0 { "even" } else { "odd" };
println!("{n} is {kind}");
// match (exhaustive)
match n {
0 => println!("zero"),
1..=3 => println!("small"),
_ => println!("other"),
}
// loop + break with value
let mut x = 0;
let result = loop {
x += 1;
if x == 3 { break x * 2; }
};
println!("result = {result}");
// while
let mut y = 0;
while y usize { // &String = shared borrow
s.len()
}
Mutable borrow (one at a time):
fn main() {
let mut s = String::from("hi");
push_exclaim(&mut s);
println!("{s}"); // "hi!"
}
fn push_exclaim(s: &mut String) { s.push('!'); }
Exercises:
- Write a function
first_char(s: &str) -> Optionusing borrowing. - Demonstrate why two simultaneous
&mutborrows fail to compile.
Further Reading:
- The Book – Ownership: https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html
- Rust By Example – Ownership/Borrowing: https://doc.rust-lang.org/rust-by-example/scope/borrow.html
1.6 References & Lifetimes
What: References point to data owned elsewhere. Lifetimes describe how long references are valid (checked at compile time).
Why: Prevent dangling references and ensure safety.
How & Code:
// Return a reference tied to input lifetime 'a
fn longest(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let s1 = String::from("abcd");
let s2 = "xyz";
let res = longest(&s1, s2);
println!("{res}");
}
Exercises:
- Write
min_ref(a: &'a i32, b: &'a i32) -> &'a i32. - Explain why returning a reference to a local variable is invalid.
Further Reading:
- The Book – Lifetimes: https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html
- RBE – Lifetimes: https://doc.rust-lang.org/rust-by-example/scope/lifetime.html
1.7 Structs, Enums & Pattern Matching
What: Custom data types; enums model variants; match destructures safely.
Why: Clear domain models and exhaustive handling.
How & Code:
struct User { name: String, age: u8 }
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
}
fn main() {
let u = User { name: "Alice".into(), age: 30 };
let m = Message::Move { x: 10, y: 20 };
match m {
Message::Quit => println!("quit"),
Message::Move { x, y } => println!("move to {x},{y}"),
Message::Write(s) => println!("write: {s}"),
}
println!("{} is {}", u.name, u.age);
}
Exercises:
- Define
enum Shape { Circle(f64), Rect{w:f64,h:f64} }and compute area withmatch. - Add a method
is_adult(&self) -> booltoUserviaimpl.
Further Reading:
- The Book – Structs: https://doc.rust-lang.org/book/ch05-00-structs.html
- The Book – Enums & Pattern Matching: https://doc.rust-lang.org/book/ch06-00-enums.html
1.8 Modules & Packages (Crates, Cargo)
What: Structure code into modules and crates. Cargo builds, tests, and manages deps.
Why: Organization, reuse, maintainability.
How:
Project layout:
my_crate/
├─ Cargo.toml
└─ src/
├─ lib.rs // library root
├─ main.rs // binary (optional)
└─ utils/
└─ mod.rs // module
Code:
// src/lib.rs
pub mod utils;
pub fn greet(name: &str) -> String { format!("Hello, {name}") }
// src/utils/mod.rs
pub fn shout(s: &str) -> String { s.to_uppercase() }
Cargo basics:
cargo new my_crate
cargo build
cargo run
cargo test
cargo add serde # (requires cargo-edit)
Exercises:
- Create a library crate with
greet(); use it fromsrc/main.rs. - Split code into
mod utils;with a helper function and call it.
Further Reading:
- The Book – Modules/Paths: https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html
- The Cargo Book: https://doc.rust-lang.org/cargo/
1.9 Mini Project — CLI Calculator
Goal: Parse num1 op num2 (e.g., 12 * 3) and print result.
Steps:
- Read a line from stdin.
- Split into tokens.
- Parse numbers; match operator.
- Handle errors gracefully.
Code (simplified):
use std::io::{self, Read};
fn calc(a: f64, op: &str, b: f64) -> Option {
match op {
"+" => Some(a + b),
"-" => Some(a - b),
"*" | "x" => Some(a * b),
"/" if b != 0.0 => Some(a / b),
_ => None,
}
}
fn main() {
println!("Enter: <a> <b>");
let mut s = String::new();
io::stdin().read_to_string(&mut s).unwrap();
let mut it = s.split_whitespace();
let a: f64 = it.next().unwrap().parse().unwrap();
let op = it.next().unwrap();
let b: f64 = it.next().unwrap().parse().unwrap();
match calc(a, op, b) {
Some(v) => println!("{a} {op} {b} = {v}"),
None => eprintln!("Invalid operation"),
}
}
Extensions: support ^ (power), parentheses, or REPL mode.
2. Working with Data & Files
2.1 Collections: Vec, HashMap, String
What: Growable containers and text types.
Why: Real apps need dynamic data and key/value maps.
How & Code:
use std::collections::HashMap;
fn main() {
// Vec
let mut v = vec![1, 2, 3];
v.push(4);
// HashMap
let mut m = HashMap::new();
m.insert("alice", 10);
m.entry("bob").or_insert(0);
// String
let mut s = String::from("hi");
s.push_str(" rust");
println!("{:?} {:?} {}", v, m, s);
}
Exercises:
- Count word frequencies using
HashMap. - Remove duplicates from a vector (use
HashSetor sort+dedup).
Further Reading:
- Collections: https://doc.rust-lang.org/book/ch08-00-common-collections.html
2.2 File I/O (read/write/serde)
What: Read and write files; serialize structs to JSON.
Why: Persist data and configs.
How & Code:
use std::fs;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
struct Note { title: String, body: String }
fn main() -> Result<(), Box> {
let n = Note { title: "Hello".into(), body: "Rust!".into() };
let json = serde_json::to_string_pretty(&n)?;
fs::write("note.json", json)?;
let data = fs::read_to_string("note.json")?;
let back: Note = serde_json::from_str(&data)?;
println!("{back:?}");
Ok(())
}
Cargo.toml:
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Exercises:
- Save a vector of
Noteto a file; load it back. - Handle missing file by creating a default one.
Further Reading:
- The Book – I/O: https://doc.rust-lang.org/rust-by-example/std_misc/file.html
- Serde: https://serde.rs/
2.3 Error Handling (Option, Result, ?)
What: Recoverable errors with Result; optional values with Option.
Why: No exceptions; explicit, type‑checked error handling.
How & Code:
fn div(a: f64, b: f64) -> Result {
if b == 0.0 { Err("divide by zero".into()) }
else { Ok(a / b) }
}
fn main() {
match div(6.0, 2.0) {
Ok(v) => println!("{v}"),
Err(e) => eprintln!("error: {e}"),
}
}
Using ?:
use std::fs;
fn read_conf(path: &str) -> Result {
let s = fs::read_to_string(path)?;
Ok(s)
}
Exercises:
- Wrap file I/O with custom error using
thiserrorcrate. - Convert
OptiontoResultwithok_or/ok_or_else.
Further Reading:
- The Book – Error Handling: https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html
- thiserror: https://docs.rs/thiserror/
2.4 Mini Project — Contact Manager
Goal: CRUD contacts stored in a JSON file.
Features: add/list/remove/search; serialize with serde.
Hints: structure a Contact { name, phone }, keep Vec in memory, write back to file on change.
3. Traits, Generics & Organization
3.1 Traits & Generics
What: Traits define shared behavior; generics allow type‑agnostic code.
Why: Reuse and abstraction without runtime cost.
How & Code:
trait Area { fn area(&self) -> f64; }
struct Rect { w: f64, h: f64 }
struct Circle { r: f64 }
impl Area for Rect { fn area(&self) -> f64 { self.w * self.h } }
impl Area for Circle { fn area(&self) -> f64 { std::f64::consts::PI * self.r * self.r } }
fn total_area(items: &[T]) -> f64 {
items.iter().map(|x| x.area()).sum()
}
Exercises:
- Implement
Perimetertrait forRectandCircle. - Use trait bounds with multiple traits (
T: Area + Clone).
Further Reading:
- The Book – Traits: https://doc.rust-lang.org/book/ch10-02-traits.html
- Generics: https://doc.rust-lang.org/book/ch10-00-generics.html
3.2 Modules, Workspaces, Re-exports
What: Organize large projects and multi‑crate repos.
Why: Scalability and clear boundaries.
Workspace Cargo.toml:
[workspace]
members = ["crates/core", "crates/cli"]
Re-export pattern:
// lib.rs
mod model;
pub use model::{User, Post}; // re-export
Further Reading:
- The Book – Packages/Crates/Modules: https://doc.rust-lang.org/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html
- Cargo Workspaces: https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html
3.3 Mini Project — Todo CLI
Goal: CRUD todos, persist to disk, modularized into model, repo, cmd.
Bonus: add tags, due dates, and filtering.
4. Concurrency & Async
4.1 Threads, Mutex, Arc, Channels
What: Parallelism and sharing safely.
Why: Utilize multiple cores without data races.
How & Code:
use std::{sync::{Arc, Mutex}, thread};
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..5 {
let c = Arc::clone(&counter);
handles.push(thread::spawn(move || {
let mut n = c.lock().unwrap();
*n += 1;
}));
}
for h in handles { h.join().unwrap(); }
println!("count = {}", *counter.lock().unwrap());
}
Further Reading:
- The Book – Concurrency: https://doc.rust-lang.org/book/ch16-00-concurrency.html
4.2 Async/Await & Tokio
What: Cooperative concurrency for I/O bound tasks.
Why: Efficient servers and clients.
How & Code:
use reqwest;
#[tokio::main]
async fn main() -> Result<(), Box> {
let body = reqwest::get("https://www.rust-lang.org").await?.text().await?;
println!("{}", &body[..80.min(body.len())]);
Ok(())
}
Further Reading:
- Async Book: https://rust-lang.github.io/async-book/
- Tokio: https://tokio.rs/
4.3 Mini Project — Simple Web Crawler
Goal: Fetch a list of URLs concurrently and print titles.
Hints: reqwest, tokio::spawn, parse HTML title with scraper crate.
5. Testing, Logging & Debugging
5.1 Unit, Integration & Doc Tests
How & Code (unit):
pub fn add(a: i32, b: i32) -> i32 { a + b }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn add_works() { assert_eq!(add(2,3), 5); }
}
Integration tests: put files in tests/ and use your_crate::*.
Doc tests: code blocks in /// comments run as tests.
Further Reading:
- The Book – Testing: https://doc.rust-lang.org/book/ch11-00-testing.html
5.2 Logging (log, tracing)
log + env_logger:
use log::{info, warn, error};
fn main() {
env_logger::init();
info!("starting");
warn!("be careful");
error!("oops");
}
Cargo.toml:
log = "0.4"
env_logger = "0.11"
tracing (structured):
use tracing::{info, instrument};
use tracing_subscriber;
#[instrument]
fn compute(x: i32) -> i32 { x * 2 }
fn main() {
tracing_subscriber::fmt().init();
info!(answer = compute(21), "done");
}
Further Reading:
- log: https://docs.rs/log/
- tracing: https://tokio.rs/blog/2019-08-tracing/
5.3 Benchmarking (criterion)
# Cargo.toml
[dev-dependencies]
criterion = "0.5"
[[bench]]
name = "my_bench"
harness = false
benches/my_bench.rs:
use criterion::{criterion_group, criterion_main, Criterion};
fn fib(n: u64) -> u64 { match n { 0|1 => n, _ => fib(n-1)+fib(n-2) } }
fn bench_fib(c: &mut Criterion) {
c.bench_function("fib 20", |b| b.iter(|| fib(20)));
}
criterion_group!(benches, bench_fib);
criterion_main!(benches);
Further Reading:
- Criterion: https://bheisler.github.io/criterion.rs/book/
5.4 Mini Project — Bug Tracker CLI
Goal: Track issues with status; log each action; tests for core logic.
Hints: enum Status { Open, Closed }, store JSON, add tracing spans.
6. Web, Databases & APIs
6.1 HTTP with Axum/Actix
Axum Hello:
use axum::{routing::get, Router};
use std::net::SocketAddr;
#[tokio::main]
async fn main() {
let app = Router::new().route("/", get(|| async { "Hello, Axum!" }));
let addr: SocketAddr = "127.0.0.1:3000".parse().unwrap();
axum::Server::bind(&addr).serve(app.into_make_service()).await.unwrap();
}
Further Reading:
- Axum: https://github.com/tokio-rs/axum
- Actix: https://actix.rs/
6.2 JSON (serde) & Validation
How & Code:
use axum::{routing::post, Router, Json};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct User { name: String }
async fn create(Json(u): Json) -> Json { Json(u) }
#[tokio::main]
async fn main() {
let app = Router::new().route("/users", post(create));
// ...
}
- Add validation with crates like
validator.
Further Reading:
- Serde: https://serde.rs/
- validator: https://docs.rs/validator/
6.3 Databases with SQLx/Diesel
SQLx example:
# Cargo.toml
sqlx = { version = "0.7", features = ["runtime-tokio", "postgres", "macros"] }
tokio = { version = "1", features = ["full"] }
use sqlx::PgPool;
#[tokio::main]
async fn main() -> Result {
let pool = PgPool::connect("postgres://user:pass@localhost/db").await?;
let rows: (i64,) = sqlx::query_as("SELECT 1").fetch_one(&pool).await?;
println!("{:?}", rows);
Ok(())
}
Further Reading:
- SQLx: https://github.com/launchbadge/sqlx
- Diesel: https://diesel.rs/
6.4 Mini Project — Blog REST API
Goal: CRUD posts, JSON endpoints, Postgres storage, error handling, logging.
Bonus: auth via JWT, pagination, OpenAPI spec with utoipa.
7. Advanced Rust
7.1 Smart Pointers (Box, Rc, RefCell, Arc, Mutex)
What/Why: Ownership patterns for heap data, shared ownership, interior mutability, concurrency.
Further Reading: https://doc.rust-lang.org/book/ch15-00-smart-pointers.html
7.2 Macros (macro_rules! & Procedural)
macro_rules!:
macro_rules! vec_of_strings {
( $( $x:expr ),* ) => { vec![ $( $x.to_string() ),* ] }
}
fn main(){ let v = vec_of_strings!("a","b"); }
Procedural (derive) overview: create custom derives with proc-macro crate.
Further Reading:
- Macros: https://doc.rust-lang.org/book/ch19-06-macros.html
- Proc macros workshop: https://github.com/dtolnay/proc-macro-workshop
7.3 Unsafe Rust & FFI
What/Why: Interop with C, manual memory when necessary; use sparingly.
FFI sketch:
extern "C" { fn puts(s: *const i8) -> i32; }
fn main() {
unsafe { puts(b"hello\0".as_ptr() as *const i8); }
}
Further Reading:
- Unsafe: https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html
- Rustonomicon: https://doc.rust-lang.org/nomicon/
8. Production: Performance, Monitoring & Deploy
8.1 Performance & Profiling
- Measure with criterion; profile with
perf/pprof-rs. - Check allocations with
valgrind/heaptrack(WSL limitations apply).
Further Reading:
- Performance Book: https://nnethercote.github.io/perf-book/
8.2 Observability (tracing, Prometheus, Grafana, Sentry)
- Use
tracingspans/fields for structured logs. - Export metrics with
prometheuscrate; scrape with Prometheus; visualize via Grafana. - Send errors to Sentry using
sentrycrate.
Further Reading:
- tracing: https://docs.rs/tracing/
- prometheus crate: https://docs.rs/prometheus/
- Grafana: https://grafana.com/
- Sentry Rust: https://docs.sentry.io/platforms/rust/
8.3 Docker & Deploy (Railway/Render/Fly.io/AWS)
Dockerfile (multi-stage):
FROM rust:1 as builder
WORKDIR /app
COPY . .
RUN cargo build --release
FROM debian:bookworm-slim
WORKDIR /app
COPY --from=builder /app/target/release/myapp /usr/local/bin/myapp
CMD ["myapp"]
Further Reading:
- Docker: https://docs.docker.com/
- Railway: https://railway.app/ • Render: https://render.com/ • Fly.io: https://fly.io/
8.4 CI/CD with GitHub Actions
.github/workflows/ci.yml:
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo build --locked --all-targets
- run: cargo test --locked --all-features
Further Reading:
- Actions: https://docs.github.com/en/actions
9. Specialized Paths
- Web Backend: Axum/Actix, SQLx/Diesel, GraphQL (async-graphql), gRPC (tonic).
- Systems/OS:
no_std, embedded-hal, RTIC, drivers; OS dev (Phil Opp’s blog). - Blockchain: Substrate, Solana, Near (Rust smart contracts).
- Game Dev: Bevy (ECS), macroquad, ggez.
- Data/ML: Polars, ndarray, Burn, Linfa.
Appendix: Command Cheatsheet
# Create / run / test
cargo new myapp
cargo run
cargo test
# Add dependencies (install cargo-edit first: cargo install cargo-edit)
cargo add serde serde_json anyhow thiserror
# Lint & fmt
rustup component add clippy rustfmt
cargo fmt -- --check
cargo clippy -- -D warnings
References
- The Rust Book: https://doc.rust-lang.org/book/
- Rust by Example: https://doc.rust-lang.org/rust-by-example/
- Rustlings: https://github.com/rust-lang/rustlings
- The Cargo Book: https://doc.rust-lang.org/cargo/
- Async Book: https://rust-lang.github.io/async-book/
- Tokio: https://tokio.rs/
- Axum: https://github.com/tokio-rs/axum
- Actix: https://actix.rs/
- SQLx: https://github.com/launchbadge/sqlx
- Diesel: https://diesel.rs/
- Criterion: https://bheisler.github.io/criterion.rs/book/
- Rustonomicon: https://doc.rust-lang.org/nomicon/
- Performance Book: https://nnethercote.github.io/perf-book/