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.
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:
$ 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
fn main() {
println!("Hello, Rust!");
}fn, notfuncorfunction. 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
mainhere — implicit()(unit).maincan also returnResult<(), 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
[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
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
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
# 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
| Command | What it does |
|---|---|
cargo new <name> | Scaffold a binary project |
cargo new <name> --lib | Scaffold a library |
cargo build | Compile in dev mode (fast, unoptimized) |
cargo build --release | Compile optimized (slow build, fast binary) |
cargo run | Build + execute |
cargo test | Run all #[test] functions |
cargo fmt | Format the codebase (rustfmt) |
cargo clippy | Linter — catches idiom issues |
cargo doc --open | Generate & open documentation |
cargo update | Refresh Cargo.lock to latest compatible versions |
7 · Common mistakes
- Forgetting
!on macros.println()doesn't exist. It'sprintln!()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 inmain()for prototypes. In production code, prefer?or explicit error handling. - Running
cargo build --releasefor development. Slow. Usecargo buildfor 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 familiar | What surprises |
|---|---|---|
| Go | One 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. |
| Python | Expressive iterator chains. | Compiled, static, no GC. Compile errors instead of runtime. |
| JavaScript | Type inference looks similar (TS). | Whole new world: ownership, traits, no nulls. |
9 · Exercises (~10 min)
- Print twice. Modify the program to print "Hello" three times. Try a
forloop. - Greet the OS. Use
std::env::consts::OSto print"Hello from linux/macos/windows". - 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. - Add a dependency.
cargo add rand(or edit Cargo.toml). Generate a random number withrand::random::<u32>().
10 · When it clicks
- You reach for
cargo newfor every prototype. - You can predict what
println!vsprintlnmeans from the bang. - You read compile errors as guidance, not punishment.
- You stop being surprised by snake_case.