NashTech Blog

Table of Contents

🦀 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

  1. Enable WSL (PowerShell as Admin):
wsl --install

Restart if asked. Launch Ubuntu from Start Menu.

  1. Update Ubuntu packages:
sudo apt update && sudo apt upgrade -y
  1. Install Rust (rustup):
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
rustc --version
cargo --version
  1. Install VS Code & Extensions (Windows side):
  • VS Code: https://code.visualstudio.com/
  • Extensions: Remote – WSL, Rust Analyzer
  1. 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 count starting 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) -> u32 returning rectangle area.
  • Write greet(name: &str) -> String that 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 &str to String, append text, and print length.
  • Safely parse "123" to i32 and 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) -> Option using borrowing.
  • Demonstrate why two simultaneous &mut borrows 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 with match.
  • Add a method is_adult(&self) -> bool to User via impl.

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 from src/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:

  1. Read a line from stdin.
  2. Split into tokens.
  3. Parse numbers; match operator.
  4. 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(&amp;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) =&gt; println!("{a} {op} {b} = {v}"),
        None =&gt; 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 HashSet or 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() -&gt; Result&lt;(), Box&gt; {
    let n = Note { title: "Hello".into(), body: "Rust!".into() };
    let json = serde_json::to_string_pretty(&amp;n)?;
    fs::write("note.json", json)?;

    let data = fs::read_to_string("note.json")?;
    let back: Note = serde_json::from_str(&amp;data)?;
    println!("{back:?}");
    Ok(())
}

Cargo.toml:

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"

Exercises:

  • Save a vector of Note to 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) -&gt; Result {
    if b == 0.0 { Err("divide by zero".into()) }
    else { Ok(a / b) }
}

fn main() {
    match div(6.0, 2.0) {
        Ok(v) =&gt; println!("{v}"),
        Err(e) =&gt; eprintln!("error: {e}"),
    }
}

Using ?:

use std::fs;

fn read_conf(path: &amp;str) -&gt; Result {
    let s = fs::read_to_string(path)?;
    Ok(s)
}

Exercises:

  • Wrap file I/O with custom error using thiserror crate.
  • Convert Option to Result with ok_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(&amp;self) -&gt; f64; }

struct Rect { w: f64, h: f64 }
struct Circle { r: f64 }

impl Area for Rect { fn area(&amp;self) -&gt; f64 { self.w * self.h } }
impl Area for Circle { fn area(&amp;self) -&gt; f64 { std::f64::consts::PI * self.r * self.r } }

fn total_area(items: &amp;[T]) -&gt; f64 {
    items.iter().map(|x| x.area()).sum()
}

Exercises:

  • Implement Perimeter trait for Rect and Circle.
  • 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(&amp;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() -&gt; Result&lt;(), Box&gt; {
    let body = reqwest::get("https://www.rust-lang.org").await?.text().await?;
    println!("{}", &amp;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) -&gt; 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) -&gt; 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) -&gt; u64 { match n { 0|1 =&gt; n, _ =&gt; fib(n-1)+fib(n-2) } }

fn bench_fib(c: &amp;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(&amp;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) -&gt; 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() -&gt; Result {
    let pool = PgPool::connect("postgres://user:pass@localhost/db").await?;
    let rows: (i64,) = sqlx::query_as("SELECT 1").fetch_one(&amp;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 ),* ) =&gt; { 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) -&gt; 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 tracing spans/fields for structured logs.
  • Export metrics with prometheus crate; scrape with Prometheus; visualize via Grafana.
  • Send errors to Sentry using sentry crate.

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 &amp; 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/

Picture of tiennguyennl

tiennguyennl

Leave a Comment

Your email address will not be published. Required fields are marked *

Suggested Article

Scroll to Top