Getting started
Start here, and stay in this order. This lesson only names the language, the tools, and the shapes of a small program. Ownership, errors, and traits come in later lessons, after you can read a file.
What Rust is
Rust is a systems programming language. You use it to write programs that sit close to memory, the CPU, and threads, without a garbage collector at runtime, and without giving up the safety that C and C++ often leave to you.
You write source files. The compiler, rustc, together with Cargo, checks ownership, types, and lifetimes before the program runs. If those checks pass, you get machine code: a native binary. There is no virtual machine the way Java or Python needs one.
Nothing is running yet. The file is a description the compiler has not accepted.
Why production teams pick it
| Reason | What you get |
|---|---|
| Memory safety | No garbage-collector pause. Use-after-free, double-free, and data races are caught at compile time. |
| Predictable performance | A high-level abstraction is supposed to compile down to tight machine code. That idea is a zero-cost abstraction. |
| Concurrency | The type system can reject a program when two threads would race. |
| Tooling | Cargo builds, tests, and publishes. rustfmt formats. clippy lints. rust-analyzer is the editor engine. |
Where it shows up
| Place | Examples |
|---|---|
| Command-line tools | ripgrep, fd, bat |
| Web backends and APIs | axum, actix, tonic for gRPC |
| Embedded and IoT | Devices with tight memory and no pause budget |
| Cloud infrastructure | Proxies, databases, observability agents |
| WebAssembly | In the browser and at the edge |
Hold one split from here on. Compile time is where Rust is strict. Runtime is where the program is fast. Other languages often say run it and fix crashes later. Rust will not compile until the dangerous cases in safe code are handled.
Languages trade how close you sit to memory for how many ways the program can hurt itself.
The rest of this course is numbered. Learn it in that order. Ownership, error handling, and traits are the parts people skip and then cannot read real code. They wait until you have bindings, types, and control flow.
fn main() {
println!("Rust: systems language + safety + speed.");
}Install and tooling
Everything after this file depends on a real toolchain. Install with rustup, not with a one-off copy of the compiler. On macOS or Linux that is the script at https://rustup.rs. Then restart the terminal, or source the Cargo env file. On Windows, download rustup-init from the same site.
Check three commands. rustc --version is the compiler. cargo --version is the build tool. rustup --version is the toolchain manager.
stable is what you ship. beta previews the next stable. nightly is experimental and only when you must. rustup default stable, then rustup update.
| Tool | Job |
|---|---|
| clippy | The linter. Treat its warnings seriously. |
| rustfmt | The formatter. One style for the team. |
| rust-analyzer | The language server. Install the Rust extension in your editor so it can talk to it. |
First-day order: install rustup, confirm rustc and Cargo, add clippy and rustfmt, connect the editor, then compile one file. From this folder, rustc 02_install_and_tooling.rs and run the binary. If it prints, the compiler works.
fn main() {
println!("If you see this, rustc works.");
println!("Install: https://rustup.rs");
println!("Then: rustup component add clippy rustfmt");
}Hello world and Cargo
A Rust program is a function named main. Execution of a binary starts there. println! prints a line to standard output. The exclamation mark means it is a macro. Macros expand at compile time. You call them like functions, but they are not functions.
You can compile one lesson file with rustc. That is useful while you are looking at a single file. Real projects always go through Cargo: cargo new creates the project, cargo run builds it and starts it. Once Cargo is set up, prefer cargo run over calling rustc yourself.
Layout to memorize
| Path | Role |
|---|---|
| Cargo.toml | Package metadata and dependencies, the role package.json plays in other stacks. |
| Cargo.lock | The exact versions. Commit it for binaries. |
| src/main.rs | The binary entry, fn main. |
| src/lib.rs | A library crate. It does not need main. |
cargo check typechecks without a full link. cargo fmt formats. cargo clippy lints. cargo doc --open builds the docs.
Printing, before any other type
| Form | Meaning |
|---|---|
println!("plain") | Writes a line to standard output |
{} | Display, the user-facing form |
{:?} | Debug, for you |
eprintln! | Standard error. CLIs use that for diagnostics. |
A string literal such as "hello" is a string slice, written &str, stored in the binary. The difference between that and an owned String waits until ownership and collections. You only need the literal for now.
fn main() {
println!("Hello, Rust.");
println!("{} + {} = {}", 2, 3, 2 + 3);
eprintln!("This line is stderr (still visible in terminal).");
}[package]
name = "hello"
version = "0.1.0"
edition = "2021"
[dependencies]How to read Rust
You do not need every keyword yet. You need to recognize shapes, in this order.
Bindings
let x = 10 is immutable. let mut y = 10 can change. const MAX: u32 = 100 is a compile-time constant, and the type is required. The word mut is the mark that you meant to change it. The next lesson spends a whole section on that.
Functions
fn add(a: i32, b: i32) -> i32 takes two 32-bit integers and returns one. The last expression without a semicolon is the return value. return a + b is an early return. It works. The expression form is the one you will see at the end of a function.
Blocks, types, macros, attributes
| Shape | How to read it |
|---|---|
| Block | Braces are a scope. A name declared inside dies at the closing brace. |
| Type | Sits after a colon: let n: i32 = 5. |
| Macro | A name ending in !. println!, format!, vec!, assert_eq!, todo!. |
| Attribute | A line starting with a hash, an instruction to the compiler. derive(Debug) generates a debug print. test marks a test. cfg(test) compiles only when testing. The hash sits immediately before the attribute name. |
let x = 1; is a statement. A trailing semicolon discards a value. x; does not give you x.
Names
| What | Case |
|---|---|
| Functions, variables, modules | snake_case |
| Types and traits | CamelCase |
| Constants and statics | SCREAMING_SNAKE |
Keywords you will meet, not all today: let, mut, fn, if, else, match, loop, while, for, struct, enum, impl, trait, use, mod, pub, crate, async, await, unsafe, return, break, continue, where, ref, move, self, Self, Result, Option, Some, None, Ok, Err. The next lesson uses let, mut, if, loop, and match. Leave the rest until the lesson that defines them.
How to study a section. Read the rules, then the code under them. Change one value, compile again, and see what the compiler says. Do not skip ownership, error handling, or traits. Those three are what interviews and production code both assume you can read.
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn main() {
let x = 10;
let mut y = 5;
y += 1;
let abs = if y >= 0 { y } else { -y };
println!("x={x}, y={y}, abs={abs}, sum={}", add(x, y));
}