Guide contents

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:

  1. Memory safety without a garbage collector.
  2. Performance comparable to C and C++, with predictable behavior and no hidden costs.
  3. Concurrency that is safe by construction ("fearless concurrency").
  4. 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:

Rust✗ does not compile · E0382
fn consume(text: String) -> usize {
    text.len()
}

fn main() {
    let report = String::from("quarterly numbers");
    let n = consume(report);
    println!("{n} bytes in {report}");
}
Output
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:

Rust
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));
}
Output
220

Generics 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.

Rust
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']));
}
Output
largest int: 9
largest float: 1.5
largest char: x

4. 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:

Rust✗ does not compile · E0499
use std::thread;

fn main() {
    let mut hits = 0;
    thread::scope(|s| {
        s.spawn(|| hits += 1);
        s.spawn(|| hits += 1);
    });
    println!("{hits}");
}
Output
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:34

The fix is to use a type that is safe to share, such as an atomic, or a Mutex:

Rust
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));
}
Output
2

The 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:

Rust
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}"),
        }
    }
}
Output
a = Some(5)
b = None
error: bad number "x": invalid digit found in string
error: unknown command: "jump"
bye

6. 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:

Rust
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}");
}
Output
30

The 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:

Rust
fn main() {
    let scores = vec![90, 85, 70];
    println!("{:?}", scores.get(1));
    println!("{:?}", scores.get(10));
}
Output
Some(85)
None

How 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, then cargo build --target ....
  • Debug and release profiles differ a lot. Debug builds compile fast and run slowly; --release turns 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-bindgen for 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: cxx for 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 ruff and uv Python 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:

  1. Start small and bounded: a new service, a CLI, or one performance-critical library, not a rewrite of the whole system.
  2. 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.
  3. Automate quality from day one in CI: cargo fmt --check, cargo clippy -- -D warnings, cargo test, and a dependency audit.
  4. 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.
  5. Agree on error handling and logging conventions: for example thiserror for libraries, anyhow for applications, and tracing for logs.
  6. Watch compile times early: use a workspace, keep crates focused, and cache builds in CI.
  7. 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.
  • unsafe exists 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, async complexity, and a younger ecosystem. It pays off for reliability-critical, performance-sensitive, and long-lived software.

Next: Installing Rust & Cargo.