28 Oct 2025

Rust fundamentals: a precise 6-week plan for systems-minded data engineers now


Rust rewards precision. This plan gets you productive fast without hand‑wavy theory. It’s optimized for an experienced engineer coming from JVM or Scala, who cares about performance, correctness, and clean mental models.

Ground rules

  • Keep it hands on. Every day ships a small artifact.
  • Read just enough, code immediately, then measure.
  • Prefer standard library and simple crates first. Add frameworks only when you feel the pain.
  • Pin to stable Rust. Let cargo choose the current edition when you create a crate; set rust-version in Cargo.toml. Use nightly only for learning tools like cargo-expand.

Day 0 - your toolchain, verified

Goal: clean install, fast feedback, and a project workspace you enjoy using.

  1. Install Xcode Command Line Tools on macOS:
1
xcode-select --install
  1. Install Rust via rustup and set stable as default:
1
2
3
4
curl https://sh.rustup.rs -sSf | sh
rustup default stable
rustc --version
cargo --version
  1. Add the standard components:
1
rustup component add clippy rustfmt llvm-tools-preview
  1. Editor setup:
  • IntelliJ IDEA: install the Rust plugin (RustRover tech) and enable rust‑analyzer.
  • Turn on format on save and Clippy on check.
  1. Quality and velocity tools you’ll actually use:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Faster test runner
cargo install cargo-nextest
# Coverage
cargo install cargo-llvm-cov
# Security advisories
cargo install cargo-audit
# Outdated and unused deps
cargo install cargo-outdated cargo-udeps
# Macro expansion and profiling
rustup toolchain install nightly
cargo install cargo-expand flamegraph
# File watching and task runner
cargo install cargo-watch just
# Cross‑compile helpers
cargo install cross
# Optional speed-ups
cargo install cargo-binstall --locked # faster installing binaries
brew install sccache || true         # macOS; or use your package manager

Create a Justfile to make common tasks one‑liners:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Justfile
fmt:
	cargo fmt --all

lint:
	cargo clippy --all-targets --all-features -D warnings

test:
	cargo nextest run

cov:
	cargo llvm-cov --workspace --html

watch:
	cargo watch -x check -x test

The 6‑week ladder

Sprint timeline (journal view)

Use the journal tasks below to track weekly progress directly inside this post. Update statuses in the front matter, and the embedded view will reflect your current state.

No structured tasks found. Add them under journal.tasks in front matter.

Each week is 5 focused days (~90 minutes/day) + one deeper weekend session. By the end you’ll have three production‑grade artifacts. Targets per week are explicit so you know you’re done.

Week 1 - ownership, borrowing, result, modules

You’ll learn: ownership moves, borrowing, lifetimes by feel, pattern matching, modules, Result and error context.

Build: CLI csv-cut that selects columns from a CSV and writes CSV to stdout (keep I/O simple this week).

  • Start a workspace data-tools with members csv-cut and libcore.
  • Add crates incrementally: csv, serde, serde_json, clap.
  • Practice: accept &str not String; return Result<T, E>; bubble errors with ?; attach context using anyhow or define a small domain error with thiserror.
  • Stretch: add --output parquet flag but wire it next week.

Targets:

  • Handles headers and empty fields; exits non‑zero on bad indices; --help prints usage.
  • Processes a 1GB CSV without panicking on your machine.

Checkpoint: explain why the borrow checker rejected a mutable and immutable borrow in the same scope, then fix it using narrower scopes or by re‑structuring data.

Week 2 - enums, traits, generics, iterators

You’ll learn: enums as algebraic data types, impl blocks, trait bounds, iterators and adapters, zero‑cost abstractions.

Build: csv-scan that streams CSV to minimal Arrow arrays (no DataFusion yet).

  • Library crate libarrow: write small helpers that convert rows to Arrow arrays/builders.
  • Add property tests with proptest and one snapshot test with insta for a tricky format edge case.
  • Micro‑bench with criterion on one hot loop (row parsing → array build).

Targets:

  • Arrow crate compiles with a minimal feature set; Criterion report shows a measurable delta after an iterator refactor.

Checkpoint: replace a for loop with iterator combinators and show the perf delta in a Criterion report.

Week 3 - concurrency and messaging

You’ll learn: threads, channels, Send and Sync, data parallelism, cancellation.

Build: parallel CSV→Parquet converter.

  • Use crossbeam-channel to decouple read/transform/write with bounded channels for backpressure.
  • Use rayon only for CPU-bound transforms; keep I/O on dedicated threads.
  • Add tracing for structured logs and spans.

Targets:

  • Converts 1GB CSV to Parquet within an acceptable wall‑time on your machine; graceful cancellation on drop of sender.

Checkpoint: run cargo flamegraph on a large file and paste the top 3 hot symbols into your notes with what you’ll try next. On macOS, cargo flamegraph may require sudo (dtrace). If blocked, use Instruments or pprof-rs.

Week 4 - async I/O and a tiny service

You’ll learn: Future, executors, async I/O, backpressure, timeouts.

Build: an axum HTTP service with two endpoints.

  • /healthz returns 200.
  • /sql starts as a stub (fixed JSON) to wire timeouts, errors, and graceful shutdown; then integrate DataFusion over local Parquet as a stretch.
  • Use Tokio runtime, graceful shutdown, request timeouts, and structured errors.

Targets:

  • Under a simple load test, p95 stays within your target at N RPS for the stub; DataFusion integration keeps p95 within a relaxed target.

Checkpoint: add a load test and confirm p95 stays within your target under parallel queries.

Week 5 - FFI and Python bridge

You’ll learn: writing a thin Rust core and exposing it to Python.

Build: a Python package fastcsv that wraps your Rust libcore using PyO3 and maturin.

  • First: maturin develop for local import; wheels later.
  • Ship two functions: infer_schema(path) -> dict, select_cols(path, cols) -> output_path.
  • Keep the Python API tiny and well‑typed.

Targets:

  • Local import works in a clean venv; function timings beat a pure‑Python baseline on a sample file.

Checkpoint: from a Python REPL, import fastcsv, run it on a sample file, compare timings vs pure Python.

Week 6 - profiling, coverage, supply‑chain hygiene, release

You’ll learn: coverage with LLVM, auditing, dependency hygiene, minimal releases.

Build: a clean release of csv-cut.

  • Add coverage gates with cargo llvm-cov (needs llvm-tools-preview).
  • Run cargo audit, cargo outdated, cargo udeps and fix issues.
  • Produce a macOS arm64 binary locally; for Linux/x86_64 use CI runners. If cross‑compiling is required, try cross (Docker/Podman needed) or cargo-zigbuild as Plan B.
  • Tag a release and attach artifacts.

Targets:

  • Reproducible build script; --version flag; binary ≤ your size budget; audit clean.

Checkpoint: a short CHANGELOG entry that states perf, memory, and behavior changes with numbers.

Capstones you can actually reuse

  1. DataFusion micro‑warehouse - run SQL on local Parquet for quick analysis and tests.
  2. High‑throughput CSV→Parquet pipeline - rayon + Arrow + Parquet writer.
  3. Python bridge - a wheel that exposes your hot path to data scientists.

The mental model that makes Rust click

  • Ownership: one owner at a time. Moves transfer ownership. Clones are explicit.
  • Borrowing: immutable borrows can alias, mutable borrow is exclusive.
  • Lifetimes: mostly inferred. Name them only when the compiler can’t.
  • Error handling: make invalid states unrepresentable with types. Use Result generously, add context, avoid unwrap in library code.

Minimal crate starter

Use this skeleton to avoid yak‑shaving (create it OUTSIDE this website repo, e.g., ~/dev/rust/data-tools):

1
2
3
4
5
mkdir -p ~/dev/rust/data-tools
cd ~/dev/rust/data-tools
cargo new csv-cut --bin
cd csv-cut
cargo add anyhow thiserror csv serde serde_json clap

main.rs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
use anyhow::{Context, Result};
use std::{fs::File, io::BufReader, path::PathBuf};

#[derive(Debug)]
struct Args { input: PathBuf, cols: Vec<usize> }

fn run(a: Args) -> Result<()> {
    let f = File::open(&a.input).with_context(|| format!("open {:?}", a.input))?;
    let mut rdr = csv::Reader::from_reader(BufReader::new(f));
    for rec in rdr.records() {
        let rec = rec?;
        let picked: Vec<&str> = a.cols.iter().filter_map(|&i| rec.get(i)).collect();
        println!("{}", picked.join(","));
    }
    Ok(())
}

fn main() {
    // parse args left as an exercise; start simple, add clap later
    let _ = run(Args { input: "data.csv".into(), cols: vec![0,2] });
}

Hands‑on project setup (outside this repo)

  1. Create your project folder: mkdir -p ~/dev/rust && cd ~/dev/rust
  2. Create the crate: cargo new csv-cut --bin && cd csv-cut
  3. Add deps: cargo add anyhow clap@4 csv serde serde_json thiserror
  4. Add rust-toolchain.toml with channel = "stable" and components clippy, rustfmt, llvm-tools-preview (optional but recommended).
  5. First run: cargo run -- --help
  6. Format/lint: cargo fmt && cargo clippy -D warnings

Sample data.csv

Small sample (for correctness):

1
2
3
4
5
6
7
8
9
cat > /tmp/data.csv <<'CSV'
id,name,city
1,Ada,NYC
2,Brandon,SF
3,Chika,Austin
CSV

# Expect: id and city
cargo run -- -c 0,2 /tmp/data.csv

Generate a larger CSV locally (approx 5M rows ~ couple hundred MB; adjust N for size):

1
2
3
4
5
6
7
8
9
python3 - <<'PY'
import csv,sys
from random import randint
N=5_000_000
w=csv.writer(sys.stdout)
w.writerow(["id","v1","v2","v3","city"]) 
for i in range(N):
    w.writerow([i, randint(0,9), randint(0,9), randint(0,9), f"C{randint(1,999)}"])
PY > /tmp/big.csv

Tiny tests for --cols and headers

Add dev-dependencies to Cargo.toml:

1
2
3
4
[dev-dependencies]
assert_cmd = "2"
predicates = "3"
tempfile = "3"

Create tests/csv_cut_smoke.rs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use assert_cmd::Command;
use predicates::prelude::*;
use std::io::Write;
use tempfile::NamedTempFile;

fn write_csv(contents: &str) -> NamedTempFile {
    let mut f = NamedTempFile::new().unwrap();
    write!(f, "{}", contents).unwrap();
    f
}

#[test]
fn selects_cols_with_headers() {
    let f = write_csv("id,name,city\n1,Ada,NYC\n2,B,LA\n");
    let mut cmd = Command::cargo_bin("csv-cut").unwrap();
    cmd.args(["-c","0,2"]).arg(f.path());
    cmd.assert()
        .success()
        .stdout(predicate::str::contains("id,city\n1,NYC\n2,LA\n"));
}

#[test]
fn respects_no_headers_flag() {
    let f = write_csv("1,Ada,NYC\n2,B,LA\n");
    let mut cmd = Command::cargo_bin("csv-cut").unwrap();
    cmd.args(["--no-headers","-c","0,2"]).arg(f.path());
    cmd.assert()
        .success()
        .stdout(predicate::str::contains("1,NYC\n2,LA\n"));
}

Run: cargo test

1–2 GB exercise (Week 1 target)

Create a bigger file (~1–2 GB) by raising N in the Python generator above (e.g., N=40_000_000 may approach ~1.5–2 GB depending on row width) and run:

1
time cargo run --release -- -c 0,2 /path/to/huge.csv > /dev/null

Note wall-time and CPU usage. If it panics or is too slow, reduce N, confirm correctness first, then profile later in Week 3.

What to read and when

  • Start here: The Rust Programming Language, Ownership, Borrowing, Traits, Error Handling.
  • Exercises: Rustlings. Pair it with Rust by Example for quick lookups.
  • Async: Asynchronous Programming in Rust and Tokio guides.
  • Web: axum examples and docs.
  • Data: Apache Arrow (minimal features) and Parquet crates, Polars docs, DataFusion user docs and datafusion-expr crate (integrate after Week 3).
  • Unsafe: The Rustonomicon. Read for literacy, not to write unsafe code.
  • Testing & Perf: proptest, insta, criterion. Coverage with cargo‑llvm‑cov. Profiling with cargo‑flamegraph.
  • Tooling: rustfmt and Clippy. Dependency checks with cargo‑audit, cargo‑outdated, cargo‑udeps. Release with cross and just enough packaging.

Common borrow checker errors and quick fixes

  • “cannot borrow as mutable because it is also borrowed as immutable”: shorten the immutable borrow’s scope, or split the data structure so borrows don’t overlap.
  • “value moved here”: pass a reference if you only need to read, or clone if cheap and correctness matters more than allocations. Better: refactor to return the value to the caller.
  • “returns a reference to data owned by the function”: return an owned value instead, or thread lifetimes through types explicitly.

Your success checklist

  • Code daily. Small loops of build, run, test.
  • Write down one thing you learned per day.
  • Every Friday: cargo audit, outdated, udeps, and one micro‑bench.
  • Every Sunday: refactor one function for clarity. No new features.

Appendix - commands you’ll reuse

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# Change to your project dir first (OUTSIDE this repo):
cd ~/dev/rust/data-tools && cargo run -p csv-cut -- --help

# Toolchain (per-repo pin)
# rust-toolchain.toml with: channel = "stable"; components = ["clippy","rustfmt","llvm-tools-preview"]

# Format and lint
cargo fmt --all
cargo clippy --all-targets --all-features -D warnings

# Tests, coverage, profiling
cargo nextest run
cargo llvm-cov --workspace --html
cargo flamegraph --bin csv-cut

# Dependency hygiene
cargo audit
cargo outdated
cargo udeps --all-targets --all-features

# Async service
cargo add axum tokio --features full tracing tracing-subscriber serde_json anyhow thiserror

# Arrow + Parquet + DataFusion (keep features minimal initially)
cargo add arrow-array arrow-schema parquet datafusion-expr

# Python FFI
cargo add pyo3 --features extension-module
# build locally first: maturin develop (inside a venv)

# Optional speed-ups
cargo binstall cargo-nextest cargo-llvm-cov cargo-audit cargo-outdated cargo-udeps --no-confirm || true

Document and commit (this repo only holds documentation)

  • Update your weekly notes in this post or add a content/posts/YYYY/MM/progress-<week>.md entry.
  • Commit and push the docs here (not your Rust code):
1
2
3
git add content/posts/2025/10/rust-fundamentals/index.md
git commit -m "Rust plan: add hands-on steps, sample data, tests, large-file exercise"
git push

Sprint board overview

The board below pulls directly from this post’s sprintboard front matter. Update story statuses, task checkboxes, or comments and the JIRA-style view refreshes on each Hugo build.

Rust Fundamentals · 6-week learning plan
🗓 Sprint 2025-W44 → 2025-W49👤 Owner Vitthal Mirji⚡ Velocity 6 stories / 18 points📅 Start 2025-10-28⏳ End 2025-12-08
Planned 3
Week 2 – Traits & Arrow adapters
arrowtraits
⏱ 3pt👤 Vitthal📅 Due 2025-11-15

Shape Arrow builders, iterators, property tests, benchmark-driven ergonomics.

0/3 tasks
  • Implement Arrow RecordBatch helpers
  • Add proptest + insta snapshot
  • Replace loops with iterators & benchmark
Week 5 – PyO3 bridge
ffipython
⏱ 3pt👤 Vitthal📅 Due 2025-12-06

Expose libcore to Python via PyO3/maturin; benchmark against pure Python.

0/3 tasks
  • Expose infer_schema/select_cols via PyO3
  • Build wheel with maturin develop
  • Benchmark vs pure Python baseline
Week 6 – Release hygiene
releasedevex
⏱ 4pt👤 Vitthal📅 Due 2025-12-08

Coverage gates, supply-chain checks, artifact release, and final metrics.

0/3 tasks
  • Enforce coverage & audit gates
  • Produce macOS/Linux binaries + changelog
  • Document perf & learning wrap-up
In Progress 2
Day 0 – Toolchain verified
setuptooling
⏱ 1pt👤 Vitthal📅 Due 2025-10-31⚑ H

Install stable toolchain, linting, velocity tools, and verify IDE feedback loop.

1/3 tasks
  • Install rustup, set default stable, add clippy/rustfmt
  • Configure IDE with rust-analyzer, format on save
  • Install cargo-nextest, llvm-cov, audit/outdated/udeps
V
Vitthal · 2025-10-28
Kickoff checklist drafted.
V
Vitthal · 2025-10-29
rustup + clippy/rustfmt installed. Next: IDE config.
Week 1 – Ownership foundations
cliownership
⏱ 3pt👤 Vitthal📅 Due 2025-11-08

CLI practice: borrow checker fluency, Result ergonomics, streaming IO.

0/3 tasks
  • Scaffold csv-cut CLI writing to stdout
  • Parse args with clap, emit structured errors
  • Prove 1–2 GB CSV throughput
Review 1
Week 4 – Async axum service
asyncaxum
⏱ 3pt👤 Vitthal📅 Due 2025-11-29

Expose DataFusion-backed queries with timeouts, tracing, and load-test targets.

1/3 tasks
  • Ship stub /sql + timeouts
  • Integrate DataFusion query executor
  • Run load test & capture p95
V
Vitthal · 2025-11-18
axum skeleton ready—pending DataFusion results review.
Blocked 1
Week 3 – Concurrency & Parquet pipeline
rayonpipeline
⏱ 3pt👤 Vitthal📅 Due 2025-11-22

Channel-based CSV → Parquet pipeline with cancellation & profiling insights.

0/3 tasks
  • Implement bounded channel stages
  • Parallel CPU transforms with rayon
  • Profile with flamegraph / Instruments
V
Vitthal · 2025-11-12
Blocked waiting on production-sized CSV sample.
Done 0
No stories in this column yet.

Usage tips

  • status controls the column (planned, in-progress, review, blocked, done).
  • tasks[].done toggles the progress bar for each story.
  • comments accept author, when, body for quick retros.
  • Customize columns or palette via sprintboard.columns.

Notes for readers: This plan sticks to stable Rust and conservative crates so you learn fundamentals first. Add frameworks only after Week 3. Keep the repo small, with one Justfile and CI that runs clippy, tests, coverage, and audit. On macOS, cargo flamegraph may require sudo (dtrace). If cross‑compiling becomes a time sink, build natively per target in CI.

What’s not covered (yet)

  • Unsafe Rust: pointer math, unsafe blocks, custom allocators. You’ll read the Rustonomicon later; you won’t write unsafe code in these 6 weeks.
  • Advanced lifetimes and pinning: self‑referential structs, Pin, projection. Useful but not needed for the artifacts here.
  • Async internals: building executors/reactors. You’ll use Tokio, not write one.
  • Proc‑macros and macro_rules! beyond basics. Powerful, but a separate track.
  • Embedded/no_std, WASM, GUI. Different ecosystems with extra setup.
  • Deep DataFusion internals (optimizer/planner extensions). You’ll use it, not modify it.
  • Complex cross‑compilation matrices/universal2 packaging. You’ll produce practical per‑target builds first; advanced packaging is a follow‑up.
Vitthal Mirji profile photo

Vitthal Mirji

Staff Data Engineer @ Walmart

Mumbai, India

Staff Data Engineer & Architect from Mumbai, India. Sharing insights on Data Engineering, Functional programming, Scala, Open source, and life.

Expertise
  • Data Engineering
  • Scala
  • Apache Spark
  • Functional Programming
  • Cloud Architecture
  • GCP
  • Big Data
Next time, we'll talk about "10 Reasons why gcc SHOULD be re-written in JavaScript - You won't believe #8!"