01 / 20 · Day 1
Day 1 · Concept 01

Hello, Rust

Install rustup. Run cargo. Write the program. Five minutes from "never wrote Rust" to "wrote your first Rust". Cargo is the one tool — it builds, tests, formats, docs, publishes. No make, no ./configure, no npm install.


1 · The intuition

Rust was started at Mozilla in 2006 by Graydon Hoare. The brief: a systems language as fast as C++, without the memory bugs. The borrow checker is the headline feature — but the day-to-day experience is shaped by cargo, Rust's build tool. One binary handles dependencies, builds, tests, documentation, and publishing.

The five commands. cargo new (scaffold a project), cargo build (compile), cargo run (build + run), cargo test (run tests), cargo fmt (format with rustfmt). Memorise these; everything else you'll look up.

2 · Try it

Install rustup first (curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh). Then:

shell terminal
$ cargo new hello
     Created binary (application) `hello` package

$ cd hello
$ tree
.
├── Cargo.toml
└── src
    └── main.rs

$ cat src/main.rs
fn main() {
    println!("Hello, world!");
}

$ cargo run
   Compiling hello v0.1.0 (/path/to/hello)
    Finished dev [unoptimized + debuginfo] target(s) in 0.42s
     Running `target/debug/hello`
Hello, world!

cargo new creates a project with a Cargo.toml (manifest) and one source file. cargo run builds and executes the binary. The first build is slow (compile rustc's internals); subsequent ones cache.

3 · The first program — and what's different

rust src/main.rs
fn main() {
    println!("Hello, Rust!");
}
  • fn, not func or function. Rust keeps keywords short.
  • println! is a macro, not a function — the ! is the marker. Macros are how Rust handles variadic and format-string magic without runtime parsing.
  • No return type on main here — implicit () (unit). main can also return Result<(), E> for fallible startup.
  • The semicolon matters. Statements end with ;. An expression without a semicolon at the end of a block is the return value.

4 · Cargo.toml — the manifest

go Cargo.toml
[package]
name = "hello"
version = "0.1.0"
edition = "2021"

[dependencies]
# Add libraries here, e.g.:
# serde = { version = "1", features = ["derive"] }
# tokio = { version = "1", features = ["full"] }

One file declares your project. edition picks a Rust language version profile (2015 / 2018 / 2021 / 2024). New projects use the latest; old code keeps working forever because editions are opt-in.

5 · Three variations

Variation 1 — read a CLI argument

rust src/main.rs · with args
use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        eprintln!("Usage: hello <name>");
        std::process::exit(1);
    }
    println!("Hello, {}!", args[1]);
}

Variation 2 — read stdin

rust src/main.rs · stdin
use std::io::{self, BufRead, Write};

fn main() {
    print!("Name? ");
    io::stdout().flush().unwrap();

    let stdin = io::stdin();
    let line = stdin.lock().lines().next().unwrap().unwrap();
    println!("Hello, {}!", line);
}

Variation 3 — bring in a crate

rust Cargo.toml + src/main.rs
# Cargo.toml — add dependency
[dependencies]
colored = "2"

# src/main.rs
use colored::Colorize;

fn main() {
    println!("{} {}!", "Hello,".green(), "Rust".red().bold());
}

Run cargo build; cargo fetches colored from crates.io, compiles it, caches it. Subsequent builds are fast.

6 · The toolchain at a glance

CommandWhat it does
cargo new <name>Scaffold a binary project
cargo new <name> --libScaffold a library
cargo buildCompile in dev mode (fast, unoptimized)
cargo build --releaseCompile optimized (slow build, fast binary)
cargo runBuild + execute
cargo testRun all #[test] functions
cargo fmtFormat the codebase (rustfmt)
cargo clippyLinter — catches idiom issues
cargo doc --openGenerate & open documentation
cargo updateRefresh Cargo.lock to latest compatible versions

7 · Common mistakes

  • Forgetting ! on macros. println() doesn't exist. It's println!() with the bang.
  • Forgetting semicolons. Rust uses them like C. Forgetting on a non-final line is a parse error.
  • Using .unwrap() in production. Fine in main() for prototypes. In production code, prefer ? or explicit error handling.
  • Running cargo build --release for development. Slow. Use cargo build for iteration; only release at ship time.
  • Capitalising function names. Snake_case for functions and variables. PascalCase for types. SCREAMING_SNAKE for consts.

8 · Coming from another language

If you know…What's familiarWhat surprises
GoOne toolchain. Single binary. Fast compile. Strong types.No GC. Borrow checker. Macros instead of metaprogramming.
C++Performance ballpark. Templates ≈ generics.Memory safety guaranteed. Modern syntax. No header files.
PythonExpressive iterator chains.Compiled, static, no GC. Compile errors instead of runtime.
JavaScriptType inference looks similar (TS).Whole new world: ownership, traits, no nulls.

9 · Exercises (~10 min)

  1. Print twice. Modify the program to print "Hello" three times. Try a for loop.
  2. Greet the OS. Use std::env::consts::OS to print "Hello from linux/macos/windows".
  3. Break the compiler. Try println("hi") without the bang. Read the error. Try omitting a semicolon. Read those errors too — Rust's compiler messages are the best part.
  4. Add a dependency. cargo add rand (or edit Cargo.toml). Generate a random number with rand::random::<u32>().

10 · When it clicks

  • You reach for cargo new for every prototype.
  • You can predict what println! vs println means from the bang.
  • You read compile errors as guidance, not punishment.
  • You stop being surprised by snake_case.
Found this useful?