Rust · lesson 1 of 30
Beginner
15- 1 Introduction to Rust
- 2 Setting Up Rust
- 3 Variables in Rust
- 4 Data Types in Rust
- 5 Operators in Rust
- 6 Control Flow in Rust
- 7 Functions in Rust
- 8 Ownership in Rust
- 9 Borrowing and References
- 11 Structs in Rust
- 12 Enums in Rust
- 13 Pattern Matching in Rust
- 17 Collections in Rust
- 25 Testing in Rust
- 26 File I/O in Rust
Intermediate
9Advanced
6Introduction to Rust
Learn what Rust is, why it matters, and where it excels — memory safety without a garbage collector.
By SkillByExample · Updated
What is Rust?
Rust is a systems programming language created at Mozilla Research and released in 2015. Its core promise is a trifecta that was previously considered impossible to achieve simultaneously: memory safety, thread safety, and performance on par with C/C++ — all without a garbage collector. Understanding this trifecta is the key to understanding why Rust exists and why it has grown so rapidly.
The language enforces its guarantees at compile time through an ownership system. Every value has exactly one owner. When the owner goes out of scope the value is freed automatically — no runtime overhead, no dangling pointers, no use-after-free bugs, no data races. The compiler rejects any program that would violate these rules, so entire classes of bugs are impossible by construction.
fn main() {
let s = String::from("hello"); // s owns the string data on the heap
println!("{}", s);
} // s goes out of scope here — memory is freed automatically, no free() needed
$ rustc --edition 2021 intro.rs -o intro && ./intro
hello
The string data is heap-allocated; when s goes out of scope at the closing brace, Rust’s
drop glue runs and the memory is freed immediately — no garbage collector required.
Why Rust Instead of C/C++?
C and C++ give you raw control but place the entire burden of memory safety on the programmer. A missing free, a double-free, or a buffer overrun can silently corrupt memory and create exploitable security vulnerabilities. These classes of bugs account for roughly 70% of CVEs in major browsers and operating systems — and they have persisted for decades despite better tooling, because the language itself offers no structural protection.
Rust’s compiler refuses to compile code that could exhibit undefined behaviour. The safety guarantee is not a runtime check — it is a compile-time proof. If the program compiles, an entire category of memory bugs simply cannot occur.
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
for (i, &byte) in bytes.iter().enumerate() {
if byte == b' ' {
return &s[0..i]; // returns a slice — a borrow of s
}
}
&s[..]
}
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s); // word borrows s
// s.clear(); // ERROR: cannot mutate s while word (a borrow) is alive
println!("first word: {}", word);
}
$ rustc --edition 2021 intro.rs -o intro && ./intro
first word: hello
This compiles and runs because the borrow of s (via word) is still alive when clear
would be called. Uncomment s.clear() and the compiler rejects the program:
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> intro.rs:14:5
|
13 | let word = first_word(&s); // word borrows s
| -- immutable borrow occurs here
14 | s.clear();
| ^^^^^^^^^ mutable borrow occurs here
15 | println!("first word: {}", word);
| ---- immutable borrow later used here
You can never hold a reference to freed or mutated data — the borrow checker enforces this at compile time, not runtime.
Why Rust Instead of Go / Java / Python?
Garbage-collected languages solve memory safety by periodically pausing the program or running a background collector. That runtime overhead matters in several important contexts:
- Latency-sensitive services — GC pauses create unpredictable jitter that breaks tight SLA requirements.
- Embedded / bare-metal — There is no runtime environment to host a garbage collector.
- WebAssembly — Binary size and startup time are critical; a GC runtime bloats both.
- OS kernels / drivers — You need full, deterministic control over every allocation and deallocation.
Rust fits all of these contexts while still offering high-level abstractions like iterators, closures, generics, and async/await — so you don’t give up expressiveness to get performance.
Key Language Features
Understanding these features at a high level will help you make sense of the tutorials that follow. Each one is covered in depth in its own section.
| Feature | What it means |
|---|---|
| Ownership | Each value has exactly one owner; freed when owner drops |
| Borrowing | References allow temporary access without transferring ownership |
| Lifetimes | Compiler tracks how long references are valid |
| Traits | Rust’s interface / typeclass system |
| Enums + pattern matching | Algebraic data types with exhaustive matching |
| Zero-cost abstractions | High-level code compiles to the same machine code as hand-written low-level code |
cargo | Integrated build system, package manager, test runner, and documentation generator |
Where Rust Is Used Today
Rust has moved well beyond research and into production at some of the world’s largest software companies. These real-world deployments are a testament to both the language’s maturity and the practical value of its guarantees.
- Firefox — large parts of the browser engine (Servo project)
- Linux kernel — Rust is now an officially supported language for kernel modules
- AWS — Firecracker VMM (powers Lambda and Fargate) is written in Rust
- Cloudflare — network proxies and edge workers
- Discord — replaced Go services for lower latency and memory use
- Microsoft — rewriting Windows components to eliminate memory-safety bugs
- Dropbox — storage backend
- WebAssembly toolchain —
wasm-pack,wasm-bindgen
The Rust Ecosystem
Rust ships with a complete, well-integrated toolchain. You rarely need to reach for third-party tools for common tasks — the official tooling covers the vast majority of the development workflow.
rustup — toolchain installer and version manager
cargo — build system and package manager
crates.io — package registry (like npm or PyPI)
docs.rs — auto-generated documentation for every crate
rust-analyzer — LSP server for IDE support
clippy — opinionated linter
rustfmt — automatic code formatter
Popular crates you will encounter early:
serde— serialization/deserialization (JSON, TOML, YAML…)tokio— async runtimereqwest— HTTP clientclap— CLI argument parsinganyhow/thiserror— ergonomic error handlingrayon— data parallelism
A Taste of Idiomatic Rust
Before diving into each topic, here is a short program that uses several Rust idioms together. Don’t worry if not every detail is clear yet — the goal is to see what idiomatic Rust looks like so you have a target in mind as you work through the tutorials.
use std::collections::HashMap;
// Accepts a &str (borrowed string slice), returns an owned HashMap
fn word_count(text: &str) -> HashMap<&str, usize> {
let mut counts = HashMap::new();
for word in text.split_whitespace() {
// entry API: insert 0 if absent, then increment
*counts.entry(word).or_insert(0) += 1;
}
counts
}
fn main() {
let text = "the quick brown fox jumps over the lazy dog the fox";
let counts = word_count(text);
// Collect into a Vec so we can sort by count (descending)
let mut pairs: Vec<(&&str, &usize)> = counts.iter().collect();
pairs.sort_by(|a, b| b.1.cmp(a.1));
for (word, count) in pairs.iter().take(3) {
println!("{}: {}", word, count);
}
}
$ rustc --edition 2021 intro.rs -o intro && ./intro
the: 3
fox: 2
lazy: 1
This snippet demonstrates borrowing, iterators, generics, and the entry API — all core Rust idioms you will learn in depth throughout this tutorial series. The first two lines are guaranteed: the and fox are the only words that appear more than once. The third line isn’t — six other words (quick, brown, jumps, over, lazy, dog) are tied at count 1, and HashMap deliberately randomizes its iteration order (a per-process random seed, chosen to resist hash-flooding attacks), so which tied word lands in third place changes on every run. Running this program five times in a row produced brown, dog, quick, over, and lazy — five different answers.
Practice
1. Predict the output
What does the word_count function return for the input "a a a b c"? Before running
it, predict the three most frequent words and their counts:
Solution
a: 3
b: 1
c: 1
The entry API inserts 0 for new keys, then += 1 increments. a appears 3 times, so it
is the only word with a count of 3, and always prints first. b and c each appear once
— they’re guaranteed to be the other two lines, but not in a guaranteed order: HashMap’s
randomized iteration means a real run just as often prints c: 1 before b: 1.
2. Extend the word counter
Modify the word_count function to ignore words shorter than 3 characters. Given the
input "the quick brown fox jumps over the lazy dog the fox", what is the new top-3
list?
Solution
the: 3
fox: 2
dog: 1
Add .filter(|w| w.len() >= 3) to the iterator chain:
for word in text.split_whitespace().filter(|w| w.len() >= 3) {
*counts.entry(word).or_insert(0) += 1;
}
The filter only drops words shorter than 3 characters — none exist in this input, so the
counts are unchanged: the: 3, fox: 2, and six words (quick, brown, jumps,
over, lazy, dog) tied at 1. the and fox are guaranteed to hold the top two spots;
the third line is whichever tied word HashMap’s randomized order happens to put first —
five runs of this exact program produced over, quick, brown, over, and dog.
3. Fix the ownership error
The following code does not compile. Without running it, identify and fix the error:
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
for (i, &byte) in bytes.iter().enumerate() {
if byte == b' ' {
return &s[0..i];
}
}
&s[..]
}
fn main() {
let s = String::from("hello world");
let w = first_word(&s);
let len = s.len();
println!("word: {}, length: {}", w, len);
}
Solution
The code actually compiles fine. first_word takes a shared reference &s and
returns a slice &str. Both w and s.len() are shared borrows — multiple shared
borrows are allowed simultaneously.
The real error occurs if you try to mutate s while w is alive:
let w = first_word(&s);
s.clear(); // ERROR: cannot borrow `s` as mutable, as it is also borrowed as immutable
4. Predict a tie-free result
Given text = "hello hello world rust rust rust", what does pairs.iter().take(4)
print? Write the expected output:
Solution
rust: 3
hello: 2
world: 1
take(4) asks for up to 4 elements, but only 3 unique words exist, so it yields 3.
Next Steps
The tutorials in this series are ordered to build concepts on top of each other. Start with Setup to install Rust, then work through variables, types, and control flow before tackling ownership — the concept that makes Rust unique. Each tutorial assumes you have read the ones before it.