Lesson 2 of 94
What Is Rust? A Complete Overview
Before writing code, it helps to know what kind of language Rust is, why it exists, and what it costs to use. This lesson is the overview a senior developer or tech lead wants first: history, design goals, the ideas that make Rust different, how the tooling and release process work, where Rust is used in production, its honest downsides, and how to decide whether it fits your team.
Rust in one paragraph
Rust is a compiled, statically typed, general-purpose language that gives you the control and speed of C and C++ with memory safety guaranteed at compile time. It has no garbage collector and no runtime to speak of. Instead, a set of rules about ownership and borrowing, checked by the compiler, decides when memory is freed and prevents whole classes of bugs before the program ever runs. Around that core it has modern features: algebraic data types, pattern matching, traits and generics, closures and iterators, async/await, and a package manager and build tool (Cargo) that ships with the language.
A short history
| Year | Event |
|---|---|
| 2006 | Graydon Hoare starts Rust as a personal project |
| 2009 | Mozilla begins sponsoring the work, to build a safer language for browser components |
| 2010 | Rust is announced publicly |
| 2015 | Rust 1.0 (May): the stability promise begins |
| 2018 | Second edition; the language starts moving toward async support |
| 2019 | async/await stabilized (Rust 1.39) |
| 2021 | The Rust Foundation is founded by AWS, Google, Huawei, Microsoft, and Mozilla; third edition |
| 2022 | Rust support merged into the Linux kernel (6.1) |
| 2025 | Rust 2024 edition (shipped with Rust 1.85) |
Two facts from that table matter for planning. Since 1.0 the language has kept a strong backward-compatibility promise, so code that compiled on an old release still compiles on a new one, apart from rare exceptions such as fixes for soundness bugs. And Rust is no longer one company's project: it is governed by an open team structure and an open RFC process, and backed by a foundation with major industry members.
The problem Rust was built to solve
For decades, software that needs speed and low-level control was written in C or C++. Those languages give you raw memory access, and with it a class of bugs that the language cannot help with:
- Use after free: using memory after it has been released.
- Double free: releasing the same memory twice.
- Buffer overflow: reading or writing past the end of an array.
- Null pointer dereference.
- Data races: two threads touching the same data, at least one writing, with no synchronization.
- Iterator invalidation: changing a collection while looping over it.
These are not rare. Microsoft and the Chromium project have each reported that roughly 70% of their serious security vulnerabilities were memory-safety bugs, and government agencies have since recommended moving to memory-safe languages.
The usual alternative is a garbage-collected language, which removes those bugs at the price of a runtime, unpredictable pause times, and higher memory use. Rust's answer is a third option: check the rules at compile time, and pay nothing at run time.
The design goals
Rust's own tagline sums it up as "A language empowering everyone to build reliable and efficient software." In practice that means four goals, and the language accepts a steeper learning curve to hold on to all of them:
- Memory safety without a garbage collector.
- Performance comparable to C and C++, with predictable behavior and no hidden costs.
- Concurrency that is safe by construction ("fearless concurrency").
- Productivity through strong types, good error messages, and excellent tooling.
The ideas that make Rust different
1. Ownership and borrowing
Every value has exactly one owner. When the owner goes out of scope, the value is dropped and its memory is freed. Assigning or passing a value moves it. You can also lend a value out with a reference (&T for reading, &mut T for writing), and the compiler enforces one rule: at any moment, a value may have either many readers or one writer, never both.
That single rule removes use-after-free, double free, iterator invalidation, and data races. It is also the part of Rust that takes the longest to learn, which is why this course spends a whole section on it. Here is the compiler stopping a use-after-move:
fn consume(text: String) -> usize {
text.len()
}
fn main() {
let report = String::from("quarterly numbers");
let n = consume(report);
println!("{n} bytes in {report}");
}error[E0382]: borrow of moved value: `report`
--> src/main.rs:8:29
|
6 | let report = String::from("quarterly numbers");
| ------ move occurs because `report` has type `String`, which does not implement the `Copy` trait
7 | let n = consume(report);
| ------ value moved here
8 | println!("{n} bytes in {report}");
| ^^^^^^ value borrowed here after move
|
note: consider changing this parameter type in function `consume` to borrow instead if owning the value isn't necessary
--> src/main.rs:1:18
|
1 | fn consume(text: String) -> usize {
| ------- ^^^^^^ this parameter takes ownership of the value
| |
| in this function
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider cloning the value if the performance cost is acceptable
|
7 | let n = consume(report.clone());
| ++++++++2. No garbage collector, and no runtime
Memory is freed at a known point: the end of the owner's scope. There are no collector pauses, no heap tuning, and no runtime to ship. A Rust program compiles to a native binary, usually a single file, that starts instantly. This is called RAII (resource acquisition is initialization), and it applies to files, sockets, and locks as well as memory: they are all released when their owner is dropped.
3. Zero-cost abstractions
High-level code, such as iterator chains, generics, and closures, compiles down to the machine code you would have written by hand. You do not pay at run time for the abstraction you did not use. This function reads like a query but compiles to a tight loop:
fn sum_of_even_squares(numbers: &[u64]) -> u64 {
numbers
.iter()
.filter(|&&n| n % 2 == 0)
.map(|&n| n * n)
.sum()
}
fn main() {
let numbers: Vec<u64> = (1..=10).collect();
println!("{}", sum_of_even_squares(&numbers));
}220Generics work through monomorphization: the compiler generates a specialized copy of a generic function for each concrete type you use. There is no boxing and no dynamic dispatch unless you ask for it with dyn Trait.
use std::fmt::Display;
fn largest<T: PartialOrd + Copy>(items: &[T]) -> T {
let mut best = items[0];
for &item in items {
if item > best {
best = item;
}
}
best
}
fn show<T: Display>(label: &str, value: T) {
println!("{label}: {value}");
}
fn main() {
show("largest int", largest(&[3, 9, 4]));
show("largest float", largest(&[1.5, 0.2]));
show("largest char", largest(&['x', 'b']));
}largest int: 9
largest float: 1.5
largest char: x4. Safe concurrency
The same ownership rules apply across threads. Two threads that write the same variable without synchronization is a compile error, not a heisenbug in production:
use std::thread;
fn main() {
let mut hits = 0;
thread::scope(|s| {
s.spawn(|| hits += 1);
s.spawn(|| hits += 1);
});
println!("{hits}");
}error[E0499]: cannot borrow `hits` as mutable more than once at a time
--> src/main.rs:7:17
|
5 | thread::scope(|s| {
| - has type `&'1 Scope<'1, '_>`
6 | s.spawn(|| hits += 1);
| ---------------------
| | | |
| | | first borrow occurs due to use of `hits` in closure
| | first mutable borrow occurs here
| argument requires that `hits` is borrowed for `'1`
7 | s.spawn(|| hits += 1);
| ^^ ---- second borrow occurs due to use of `hits` in closure
| |
| second mutable borrow occurs here
|
note: requirement that the value outlives `'1` introduced here
--> /rustc/e408947bfd200af42db322daf0fadfe7e26d3bd1/library/std/src/thread/scoped.rs:200:34The fix is to use a type that is safe to share, such as an atomic, or a Mutex:
use std::sync::atomic::{AtomicU32, Ordering};
use std::thread;
fn main() {
let hits = AtomicU32::new(0);
thread::scope(|s| {
s.spawn(|| hits.fetch_add(1, Ordering::Relaxed));
s.spawn(|| hits.fetch_add(1, Ordering::Relaxed));
});
println!("{}", hits.load(Ordering::Relaxed));
}2The traits Send and Sync mark which types can move between threads or be shared, and the compiler checks them for you. Concurrency is covered in its own section later.
5. Types that model your domain
Rust has enums that carry data (sum types), pattern matching with compile-time exhaustiveness checks, and no null. A value that might be missing is an Option<T>, and an operation that might fail returns a Result<T, E>. There are no exceptions: errors are values, and the compiler makes you handle them or explicitly pass them on with ?. This program parses commands, and every case (success, parse failure, unknown command) is handled:
use std::collections::HashMap;
#[derive(Debug)]
enum Command {
Set(String, i64),
Get(String),
Quit,
}
fn parse(line: &str) -> Result<Command, String> {
let parts: Vec<&str> = line.split_whitespace().collect();
match parts.as_slice() {
["set", key, value] => {
let n = value.parse::<i64>().map_err(|e| format!("bad number {value:?}: {e}"))?;
Ok(Command::Set(key.to_string(), n))
}
["get", key] => Ok(Command::Get(key.to_string())),
["quit"] => Ok(Command::Quit),
_ => Err(format!("unknown command: {line:?}")),
}
}
fn main() {
let mut store: HashMap<String, i64> = HashMap::new();
for line in ["set a 5", "get a", "get b", "set a x", "jump", "quit"] {
match parse(line) {
Ok(Command::Set(k, v)) => {
store.insert(k, v);
}
Ok(Command::Get(k)) => println!("{k} = {:?}", store.get(&k)),
Ok(Command::Quit) => println!("bye"),
Err(msg) => println!("error: {msg}"),
}
}
}a = Some(5)
b = None
error: bad number "x": invalid digit found in string
error: unknown command: "jump"
bye6. Traits instead of inheritance
Rust has no classes and no inheritance. You define data with struct and enum, add behavior with impl blocks, and share behavior through traits (similar to interfaces, but they can also be implemented for types you did not write, and can provide default methods). Polymorphism comes from generics with trait bounds (static dispatch) or trait objects (dyn Trait, dynamic dispatch).
7. Immutability by default and explicit everything
Variables are immutable unless you write mut. Integer types have explicit sizes (i32, u64, usize). There are no implicit numeric conversions. Cost is visible in the code: a .clone() call, a Box, or an Rc tells you an allocation or a copy may happen.
Is it really safe? unsafe and the escape hatch
Everything above holds in safe Rust. Some things cannot be checked by the compiler: calling C code, talking to hardware, or implementing a low-level data structure such as Vec itself. For those, Rust has an unsafe block, which unlocks a small set of extra operations, such as dereferencing a raw pointer:
fn main() {
let numbers = [10, 20, 30];
let ptr = numbers.as_ptr();
// SAFETY: `ptr` points to the start of a 3-element array that is alive,
// and index 2 is within bounds.
let last = unsafe { *ptr.add(2) };
println!("{last}");
}30The design principle is safe abstractions over unsafe code. The standard library is full of unsafe internals wrapped in safe APIs, so you can build a large application without writing a single unsafe block. When a bug does appear in an application, unsafe gives you a short list of places to audit instead of the whole codebase. Many teams forbid unsafe in application code with #![forbid(unsafe_code)] and confine it to reviewed, well-tested modules.
Safe Rust does not prevent every bug. Logic errors, deadlocks, memory leaks (with Rc cycles, for example), integer overflow in release builds, and panics from unwrap() are all still possible. Rust removes the memory-safety and data-race classes; it does not replace design and testing.
Out-of-bounds indexing is checked at run time and panics rather than reading random memory, and get returns an Option when you would rather handle it:
fn main() {
let scores = vec![90, 85, 70];
println!("{:?}", scores.get(1));
println!("{:?}", scores.get(10));
}Some(85)
NoneHow Rust code becomes a program
rustc, the Rust compiler, is written in Rust and uses LLVM as its backend, the same optimizer used by Clang and Swift. The pipeline is: parse, expand macros, type-check and borrow-check, lower to an intermediate representation (MIR), then hand the result to LLVM to generate optimized machine code. In practice you never call rustc directly; you call cargo build.
Consequences that matter in practice:
- Ahead-of-time compilation to native code for many targets: Linux, macOS, Windows, Android, iOS, WebAssembly, and bare-metal embedded chips.
- Static linking by default for Rust dependencies, so a service is often a single self-contained binary, easy to put in a small container image.
- Cross-compilation is built in: add a target with
rustup target add, thencargo build --target .... - Debug and release profiles differ a lot. Debug builds compile fast and run slowly;
--releaseturns on optimizations. Never benchmark a debug build. - Compile times are the price. Rust does a great deal of analysis, and large projects can take minutes to build from scratch. Incremental builds, caching, and a few habits help; see Compile Times & Builds.
The toolchain and ecosystem
Rust ships with a tool set that most languages leave to third parties:
| Tool | What it does |
|---|---|
rustup |
Installs and switches compiler versions (stable, beta, nightly) and targets |
cargo |
Build tool, package manager, test runner, and task runner in one |
crates.io |
The public package registry, with well over 100,000 crates |
docs.rs |
Auto-generated documentation for every published crate |
rustfmt |
The standard code formatter, so style is not debated in reviews |
clippy |
A linter with hundreds of checks for correctness, style, and performance |
rust-analyzer |
The language server behind editor completion, navigation, and refactoring |
rustdoc |
Builds documentation from /// comments, and runs the examples in them as tests |
cargo test |
Unit tests live next to the code; integration tests and doc-tests are built in |
miri |
Interprets your program to detect undefined behavior in unsafe code |
Dependencies are declared in Cargo.toml, use semantic versioning, and are pinned exactly in Cargo.lock, which you commit for applications. Feature flags let a crate offer optional functionality and keep default builds small. Supply-chain tools such as cargo audit, cargo deny, and cargo vet are covered in Dependencies, Security & CI.
Widely used crates you will meet in this course include serde (serialization), tokio (the async runtime), axum (web framework), reqwest (HTTP client), sqlx (databases), clap (command-line parsing), tracing (structured logging), and anyhow and thiserror (error handling).
Releases, editions, and stability
Rust follows a six-week release train. Every change flows from nightly, to beta, to stable, and a new stable version appears on a fixed schedule. Upgrading is usually uneventful, and many teams simply track the latest stable.
Backward-incompatible language changes are gathered into editions, released about every three years: 2015, 2018, 2021, and 2024. An edition is opt-in per crate, set in Cargo.toml. Crates on different editions link together without trouble, so a project can upgrade one crate at a time and still use dependencies written for older editions. cargo fix --edition automates most of the migration.
Language and library changes go through a public RFC process and are run by volunteer and sponsored teams (language, compiler, libraries, Cargo, and others). Features can be used on stable only after they are stabilized, and experimental ones stay on nightly behind feature flags. For libraries, crates commonly declare a minimum supported Rust version (MSRV) so consumers know what compiler they need.
Platforms and interoperability
Rust groups targets into tiers: Tier 1 targets (such as 64-bit Linux, macOS, and Windows) are guaranteed to build and pass tests, Tier 2 targets are guaranteed to build, and Tier 3 targets are community-supported. Across those tiers Rust runs from servers to microcontrollers:
- Servers and CLIs: the mainstream use.
- WebAssembly: run in a browser or an edge runtime, with
wasm-bindgenfor JavaScript interop. - Embedded: with
#![no_std], Rust runs without an operating system. See no_std & Embedded Rust. - Existing codebases: Rust talks to C through a stable FFI, and there are mature bridges to other ecosystems:
cxxfor C++, PyO3 for Python, and napi-rs for Node.js. A common adoption path is rewriting one hot component as a Rust library called from an existing application. See Unsafe Rust & FFI.
Where Rust is used
Rust has moved from an experiment to a production language across the industry. Examples that are publicly documented:
- Operating systems: the Linux kernel (drivers and subsystems), Android (new native code), and Windows components.
- Cloud and infrastructure: AWS Firecracker (the microVM behind Lambda and Fargate), Cloudflare's Pingora proxy, and many databases and message queues.
- Web and product companies: Discord, Figma, Dropbox, and 1Password use Rust for performance-critical or security-critical parts.
- Developer tools: ripgrep, fd, bat, the
ruffanduvPython tools, and JavaScript tooling such as SWC and Turbopack, chosen for speed. - Browsers and runtimes: parts of Firefox (the Stylo CSS engine) and the Deno runtime.
- Blockchain, games, and embedded: where predictable performance and correctness matter.
Rust has also ranked at or near the top of the Stack Overflow developer survey's "most admired" languages for many years running.
The honest costs
A good lead knows the trade-offs before committing a team. These are the real ones:
| Cost | What to expect |
|---|---|
| Learning curve | Ownership, borrowing, and lifetimes are new ideas for most developers. Expect weeks to become productive and several months to feel fluent. The compiler's error messages are good, and they are how you learn. |
| Compile times | Slower than Go or interpreted languages; large workspaces need caching and care. |
| Development speed for simple code | For plain CRUD or scripting, a garbage-collected language is often faster to write. Rust pays off where correctness, performance, or resource use matter. |
| Async complexity | async Rust is powerful, but has more concepts (Pin, Send bounds, runtimes) than the rest of the language. This course has a full section on it. |
| Ecosystem gaps | The ecosystem is younger. Areas such as GUI frameworks, scientific computing, and some enterprise integrations are less mature than in older languages. Check for the libraries you need before you start. |
| Hiring | The pool of experienced Rust developers is smaller, though many teams successfully train strong developers from other languages. |
| Binary size and build memory | Release binaries can be larger than C, and full builds can be memory-hungry. Both are tunable. |
| Dynamic patterns | Graph structures, shared mutable state, and deeply object-oriented designs need different approaches (indices, Rc<RefCell<T>>, message passing). |
When to choose Rust
| Good fit | Weaker fit |
|---|---|
| Low-latency services, proxies, and data planes | Quick prototypes and throwaway scripts |
| Command-line tools and developer tooling | Apps that are mostly forms over a database, with a strict deadline and a team new to Rust |
| Anything security-sensitive that parses untrusted input | Heavy data-science or ML work (use Python, and call Rust for the hot loops) |
| Embedded and resource-constrained systems | Projects that depend on libraries only available in another ecosystem |
| WebAssembly modules and shared libraries used from other languages | UI-heavy desktop or mobile apps (possible, but the tooling is less mature) |
| Long-lived services where reliability and cost of ownership matter | Teams that cannot afford any ramp-up time |
Rust is often chosen over C and C++ for safety with equal speed, over Go when you need lower latency, no garbage-collector pauses, or a richer type system, and as a companion to Python or JavaScript for performance-critical parts.
Adopting Rust on a team
If you are the one deciding, these practices come up repeatedly in successful adoptions:
- Start small and bounded: a new service, a CLI, or one performance-critical library, not a rewrite of the whole system.
- Invest in learning: pair experienced and new Rust developers, and do code review that explains why the compiler said what it said. Working through Ownership & Borrowing first pays off.
- Automate quality from day one in CI:
cargo fmt --check,cargo clippy -- -D warnings,cargo test, and a dependency audit. - Set policies early: a minimum supported Rust version, rules for
unsafe(allowed only in reviewed modules, always with a// SAFETY:comment), and how to vet new dependencies. - Agree on error handling and logging conventions: for example
thiserrorfor libraries,anyhowfor applications, andtracingfor logs. - Watch compile times early: use a workspace, keep crates focused, and cache builds in CI.
- Measure before optimizing: Rust is fast by default, and profiling shows where the real costs are.
The last section of this course, Production Rust, turns these into concrete lessons.
A glossary of terms you will meet
| Term | Meaning |
|---|---|
| Crate | A compilation unit: a library or a binary. The unit of packaging on crates.io |
| Package | One or more crates described by a Cargo.toml |
| Workspace | Several packages built together with a shared Cargo.lock |
| Module | A namespace inside a crate |
| Ownership | Every value has exactly one owner, and it is dropped when the owner goes out of scope |
| Move | Transferring ownership; the old variable can no longer be used |
| Borrow | A temporary reference (&T or &mut T) to a value you do not own |
| Lifetime | How long a reference is valid, checked by the compiler and written 'a when it must be named |
| Trait | A set of methods a type can implement; used for interfaces, bounds, and dynamic dispatch |
| Macro | Code that generates code, such as println! and vec! |
Option / Result |
The standard types for "maybe absent" and "maybe failed" |
unsafe |
Marks code whose safety the programmer, not the compiler, is responsible for |
| Edition | An opt-in language version (2015, 2018, 2021, 2024) |
| MSRV | Minimum supported Rust version of a crate |
| Nightly / stable | Release channels; experimental features exist only on nightly |
What you will learn in this course
The introduction lists all 19 sections. The next lesson installs the toolchain, and by the end of the Getting Started section you will be running and formatting real programs. From there the course moves through the language in order: the basics, then ownership and borrowing (the part that makes Rust Rust), data types, traits, collections, error handling, concurrency, async, memory, and production practice.
Summary
- Rust is a compiled, statically typed language with C-like performance and memory safety checked at compile time, without a garbage collector.
- Its core idea is ownership and borrowing: one owner per value, and either many readers or one writer at a time. This also makes data races a compile error.
- It offers zero-cost abstractions, enums with data, pattern matching, traits, no
null, and errors as values. unsafeexists for the few things the compiler cannot verify, and is wrapped in safe APIs.- Cargo,
rustfmt,clippy,rust-analyzer, and built-in testing ship with the language. Releases come every six weeks, editions every three years, and stability is a promise. - Rust runs from servers to browsers (WebAssembly) to microcontrollers, and interoperates with C, C++, Python, and JavaScript.
- The real costs are the learning curve, compile times,
asynccomplexity, and a younger ecosystem. It pays off for reliability-critical, performance-sensitive, and long-lived software.
Next: Installing Rust & Cargo.
