|$ curl https://forge-ai.dev/api/markdown?path=docs/rust
$cat docs/rust-—-overview.md
updated Recently·28 min read·published

Rust — Overview

RustSystems ProgrammingBeginner to Intermediate🎯Free Tools
Introduction

Rust is a systems programming language that guarantees memory safety and thread safety without relying on a garbage collector. Originally developed at Mozilla Research, it is now maintained by the Rust Project and used in everything from operating systems and game engines to web services and command-line tools.

The language achieves its safety guarantees through a strict ownership model enforced at compile time. This eliminates entire classes of bugs — null pointer dereferences, dangling pointers, data races — before your program ever runs. The cost is a steeper learning curve than managed languages, but the payoff is predictable performance and robust concurrency.

info

Think of Rust as C++ with a borrow checker. You still get zero-cost abstractions, deterministic memory management, and direct hardware control, but the compiler refuses to compile code that violates memory or thread safety rules.
Why Rust?

Rust targets the same use cases as C and C++ but with modern tooling, a strong type system, and built-in package management. Companies adopt Rust when they need performance, reliability, and fearless concurrency in the same codebase.

FeatureBenefitExample Use Case
Memory safetyNo dangling pointers, use-after-free, or double-free bugsBrowsers, operating systems, embedded firmware
Zero-cost abstractionsHigh-level features compile to efficient machine codeGame engines, simulations, high-frequency data pipelines
Fearless concurrencyData races caught at compile timeWeb servers, async runtimes, parallel compute
Pattern matchingExhaustive, expressive branchingParsers, state machines, compilers
CargoBuild, test, and dependency management in one toolEvery Rust project, from scripts to large applications
Cross-compilationBuild for many targets from one hostWebAssembly, embedded ARM, cloud containers
Installation & Setup

The official Rust toolchain installer is rustup. It installs the compiler rustc, the build tool cargo, the standard library documentation, and the ability to switch between release channels.

install-rust.sh
Bash
1# macOS, Linux, and WSL
2curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
3
4# Follow the prompts, then reload your shell
5source "$HOME/.cargo/env"
6
7# Verify the installation
8rustc --version
9cargo --version

Rustup supports stable, beta, and nightly compilers. Most production work should stay on stable. Nightly is only required for experimental features or specific tooling such as certain sanitizers or benchmark frameworks.

rustup-commands.sh
Bash
1# Install an additional target, e.g. WebAssembly
2rustup target add wasm32-unknown-unknown
3
4# Update to the latest stable toolchain
5rustup update stable
6
7# Show installed toolchains
8rustup toolchain list

warning

Do not install Rust through a system package manager unless you have a specific reason. Distribution packages often lag behind the official release and may miss components like rustfmt, clippy, or source maps for debugging.
Hello World

Every Rust program starts with a main function. Statements end with semicolons, and the exclamation mark in println! indicates a macro, not a normal function call.

main.rs
RUST
1// main.rs
2fn main() {
3 println!("Hello, ForgeLearn!");
4}
run-hello.sh
Bash
1# Compile and run directly
2rustc main.rs
3./main
4
5# Or with Cargo
6cargo new hello --bin
7cd hello
8cargo run

Rust programs are statically typed, but local variable types are usually inferred. Type annotations are required when inference is ambiguous or when you want to document intent explicitly.

variables.rs
RUST
1fn main() {
2 // Type inferred as i32
3 let answer = 42;
4
5 // Explicit type annotation
6 let pi: f64 = 3.14159;
7
8 // Immutable by default
9 let name = "Rust";
10
11 // Mutable variable
12 let mut counter = 0;
13 counter += 1;
14
15 println!("{answer}, {pi}, {name}, {counter}");
16}

best practice

Prefer let without mut until you genuinely need mutation. Immutability is the default in Rust and makes code easier to reason about, especially once references and concurrency enter the picture.
Cargo: Build Tool & Package Manager

Cargo is Rust's unified build system, package manager, test runner, and documentation generator. A Cargo project is a crate, and crates are described by a Cargo.toml manifest.

Cargo.toml
TOML
1[package]
2name = "forgelearn-demo"
3version = "0.1.0"
4edition = "2021"
5authors = ["ForgeLearn <team@forgelearn.dev>"]
6description = "A demo Rust crate for learning"
7license = "MIT"
8
9[dependencies]
10serde = { version = "1.0", features = ["derive"] }
11tokio = { version = "1", features = ["full"] }
12
13[dev-dependencies]
14assert_cmd = "2"
CommandPurposeOutput
cargo new NAMECreate a new binary crateA directory with src/main.rs and Cargo.toml
cargo buildCompile the projectDebug binary in target/debug/
cargo runBuild and execute the binaryCompiled output and program stdout
cargo testRun unit and integration testsTest report with pass/fail counts
cargo checkType-check without producing a binaryFaster feedback during development
cargo clippyRun the linterWarnings about idiomatic issues
cargo doc --openGenerate and open documentationLocal HTML docs for your crate

Cargo follows conventions. Source files live in src/, integration tests in tests/, benchmarks in benches/, and example binaries in examples/. Respecting these conventions lets Cargo discover and run everything automatically.

lib.rs
RUST
1// src/lib.rs — library crate root
2pub fn greet(name: &str) -> String {
3 format!("Hello, {name}!")
4}
5
6#[cfg(test)]
7mod tests {
8 use super::*;
9
10 #[test]
11 fn test_greet() {
12 assert_eq!(greet("Rust"), "Hello, Rust!");
13 }
14}
Ownership Basics

Ownership is Rust's most distinctive feature. Every value has a single owner, and when the owner goes out of scope, the value is dropped. This rule enables deterministic cleanup without a garbage collector.

ownership-move.rs
RUST
1fn main() {
2 let s1 = String::from("hello"); // s1 owns the String
3 let s2 = s1; // ownership moves to s2
4
5 // println!("{s1}"); // ERROR: value borrowed after move
6 println!("{s2}"); // OK
7}

The ownership rules are simple but strict: each value has one owner at a time; when the owner drops, the value drops; and ownership can be transferred (moved) or borrowed but never duplicated implicitly for heap data.

RuleMeaningCompiler Error If Broken
One ownerA value is owned by exactly one bindingUse of moved value
Drop on scope exitDestructor runs automatically at end of scopeResource leaks are prevented by type system
Move by defaultAssigning a heap value transfers ownershipBorrow checker flags invalid reuse

Primitive types such as integers and booleans implement the Copy trait, so they are copied rather than moved. Heap-allocated types like String and Vec do not implement Copy and therefore move.

ownership-functions.rs
RUST
1fn main() {
2 let x = 5;
3 let y = x; // Copy — both x and y are valid
4 println!("{x} {y}");
5
6 let s = String::from("owned");
7 takes_ownership(s); // s moves into the function
8 // println!("{s}"); // ERROR
9
10 let n = 42;
11 makes_copy(n); // n is copied
12 println!("{n}"); // OK
13}
14
15fn takes_ownership(value: String) {
16 println!("{value}");
17} // value dropped here
18
19fn makes_copy(value: i32) {
20 println!("{value}");
21}

warning

If you need to keep using a value after passing it to a function, either return ownership back, pass a reference, or implement/derive Clone and call .clone(). Blindly cloning everywhere hurts performance; prefer references when possible.
Borrowing & References

Borrowing lets code access data without taking ownership. A reference is created with & and dereferenced with *. The borrow checker enforces rules that prevent data races and use-after-free bugs.

borrowing.rs
RUST
1fn main() {
2 let s = String::from("borrowed");
3 let len = calculate_length(&s);
4
5 println!("'{s}' has length {len}");
6}
7
8fn calculate_length(value: &String) -> usize {
9 value.len()
10}

Rust's borrowing rules are enforced for the entire program, not just single functions. At any moment, for a given piece of data, you may have either one mutable reference or any number of immutable references, and references must always be valid.

RuleExampleWhy It Matters
One mutable OR many immutableCannot borrow mutably while immutably borrowedPrevents readers from observing torn writes
References must be validCannot return a reference to a local variableEliminates dangling pointers
Mutable borrows are exclusiveTwo &mut to the same data are forbiddenGuarantees no data races at compile time
mutable-borrow.rs
RUST
1fn main() {
2 let mut s = String::from("hello");
3
4 change(&mut s);
5 println!("{s}");
6
7 let r1 = &s;
8 let r2 = &s;
9 println!("{r1} {r2}"); // OK: multiple immutable borrows
10
11 // let r3 = &mut s; // ERROR: cannot borrow mutably while immutably borrowed
12}
13
14fn change(value: &mut String) {
15 value.push_str(", world");
16}

best practice

Use immutable references by default. Introduce &mut only when mutation is necessary, and keep the mutation scope as small as possible. Smaller borrow scopes reduce fights with the borrow checker and make the code easier to refactor.
Slices

A slice is a reference to a contiguous sequence of elements. String slices (&str) and array slices (&[T]) let you work with parts of a collection without copying data.

slices.rs
RUST
1fn main() {
2 let s = String::from("hello world");
3 let hello = &s[0..5];
4 let world = &s[6..11];
5
6 println!("{hello} {world}");
7
8 let arr = [1, 2, 3, 4, 5];
9 let slice = &arr[1..3];
10 println!("{:?}", slice);
11}
12
13fn first_word(s: &str) -> &str {
14 match s.find(' ') {
15 Some(idx) => &s[..idx],
16 None => s,
17 }
18}

info

Prefer &str over &String in function signatures. &str accepts both string slices and borrowed Strings, making your functions more flexible.
Structs

Structs group related data into named fields. They are the primary way to define custom compound types in Rust. You can attach methods to structs using impl blocks.

structs.rs
RUST
1struct User {
2 username: String,
3 email: String,
4 active: bool,
5 sign_in_count: u64,
6}
7
8fn main() {
9 let mut user = User {
10 username: String::from("forgelearn"),
11 email: String::from("team@forgelearn.dev"),
12 active: true,
13 sign_in_count: 1,
14 };
15
16 user.sign_in_count += 1;
17
18 println!("{} has signed in {} times", user.username, user.sign_in_count);
19}

Rust also supports tuple structs and unit structs. Tuple structs are useful for newtypes that wrap a primitive value, while unit structs carry no data but can implement traits.

struct-variants.rs
RUST
1struct Point(i32, i32, i32);
2struct AlwaysEqual;
3
4impl User {
5 fn new(username: &str, email: &str) -> Self {
6 Self {
7 username: username.to_string(),
8 email: email.to_string(),
9 active: true,
10 sign_in_count: 0,
11 }
12 }
13
14 fn deactivate(&mut self) {
15 self.active = false;
16 }
17}
18
19fn main() {
20 let origin = Point(0, 0, 0);
21 let _marker = AlwaysEqual;
22
23 let mut user = User::new("learner", "learner@example.com");
24 user.deactivate();
25 println!("active = {}", user.active);
26}

best practice

Use the Self alias inside impl blocks. It reduces repetition and makes renaming the struct later trivial. Derive Debug early with #[derive(Debug)] so you can print values during development.
Enums & Variants

Enums in Rust are algebraic data types. Each variant can carry data, making them far more expressive than simple tagged unions in other languages. The standard library enums Option and Result are used everywhere.

enums.rs
RUST
1enum Message {
2 Quit,
3 Move { x: i32, y: i32 },
4 Write(String),
5 ChangeColor(u8, u8, u8),
6}
7
8fn main() {
9 let msg = Message::Move { x: 10, y: 20 };
10 describe(&msg);
11}
12
13fn describe(msg: &Message) {
14 match msg {
15 Message::Quit => println!("Quit"),
16 Message::Move { x, y } => println!("Move to ({x}, {y})"),
17 Message::Write(text) => println!("Write: {text}"),
18 Message::ChangeColor(r, g, b) => println!("Color({r}, {g}, {b})"),
19 }
20}

Option<T> replaces null with explicit absence handling, and Result<T, E> replaces exceptions with recoverable errors. Both force callers to acknowledge the possibility of missing values or failures.

option-result.rs
RUST
1fn main() {
2 let maybe_number: Option<i32> = Some(7);
3 let no_number: Option<i32> = None;
4
5 println!("{}", maybe_number.unwrap_or(0));
6 println!("{}", no_number.unwrap_or(0));
7
8 let ok_result: Result<i32, &str> = Ok(42);
9 let err_result: Result<i32, &str> = Err("something went wrong");
10
11 println!("{:?}", ok_result);
12 println!("{:?}", err_result);
13}
Pattern Matching

Rust's match expression is exhaustive: every possible variant must be handled. The compiler rejects incomplete matches, which prevents bugs when new variants are added later.

pattern-match.rs
RUST
1enum Coin {
2 Penny,
3 Nickel,
4 Dime,
5 Quarter(String), // state name
6}
7
8fn value_in_cents(coin: Coin) -> u8 {
9 match coin {
10 Coin::Penny => 1,
11 Coin::Nickel => 5,
12 Coin::Dime => 10,
13 Coin::Quarter(state) => {
14 println!("Quarter from {state}!");
15 25
16 }
17 }
18}

The if let and while let constructs handle a single pattern without the boilerplate of a full match. The _ wildcard ignores unmatched variants.

if-let.rs
RUST
1fn main() {
2 let config_max: Option<u8> = Some(3u8);
3
4 if let Some(max) = config_max {
5 println!("Maximum is configured to {max}");
6 }
7
8 let mut stack = Vec::new();
9 stack.push(1);
10 stack.push(2);
11 stack.push(3);
12
13 while let Some(top) = stack.pop() {
14 println!("{top}");
15 }
16
17 let x = 1;
18 match x {
19 1 => println!("one"),
20 2 => println!("two"),
21 _ => println!("something else"),
22 }
23}

best practice

Avoid .unwrap() and .expect() in production code except for cases that truly cannot happen. Prefer match, if let, or combinators like map and and_then to keep error handling explicit.
Modules & Visibility

Modules organize code into logical units and control visibility. By default, items in a module are private. Use the pub keyword to expose them to parent modules or external crates.

modules.rs
RUST
1// src/lib.rs
2mod network {
3 pub fn connect() {
4 println!("Connected");
5 }
6
7 pub mod client {
8 pub fn ping() {
9 println!("Ping");
10 }
11 }
12}
13
14pub fn run() {
15 network::connect();
16 network::client::ping();
17}
18
19// main.rs or another crate
20use mycrate::run;
21
22fn main() {
23 run();
24}

Cargo automatically turns files in src/ into modules based on the filesystem. A file src/front_of_house.rs becomes a module front_of_house, and a directory src/front_of_house/ with a mod.rs or sibling hosting.rs creates nested modules.

module-filesystem.rs
RUST
1// src/front_of_house.rs
2pub mod hosting;
3
4// src/front_of_house/hosting.rs
5pub fn add_to_waitlist() {}
6
7// src/lib.rs
8mod front_of_house;
9
10pub use front_of_house::hosting::add_to_waitlist;
11
12pub fn eat_at_restaurant() {
13 add_to_waitlist();
14}

info

Use use to bring paths into scope, and pub use to re-export items so consumers see a cleaner API. Group imports consistently: standard library first, then external crates, then your own modules.
Error Handling

Rust does not have exceptions. Recoverable errors are represented by Result<T, E>, and unrecoverable errors trigger a panic. This distinction forces developers to decide at every call site whether a failure is expected or fatal.

error-handling.rs
RUST
1use std::fs::File;
2use std::io::{self, Read};
3
4fn read_username_from_file(path: &str) -> Result<String, io::Error> {
5 let mut file = File::open(path)?;
6 let mut username = String::new();
7 file.read_to_string(&mut username)?;
8 Ok(username)
9}
10
11fn main() {
12 match read_username_from_file("hello.txt") {
13 Ok(username) => println!("{username}"),
14 Err(e) => eprintln!("Failed to read username: {e}"),
15 }
16}

The ? operator propagates errors early. It works in functions that return Result, Option, or another type that implements FromResidual. It is the idiomatic replacement for manually writing match chains.

question-mark.rs
RUST
1fn last_char_of_first_line(text: &str) -> Option<char> {
2 text.lines().next()?.chars().last()
3}
4
5fn main() {
6 let input = "hello
7world";
8 println!("{:?}", last_char_of_first_line(input));
9}
ApproachWhen to UseExample
unwrap / expectTests, prototypes, truly invariant casesHardcoded config that must exist
matchWhen every branch needs distinct logicCustom retry or fallback behavior
if let / while letSingle variant handlingOptional logging or extraction
?Error propagation in fallible functionsI/O or parsing helper functions
Result combinatorsFunctional composition without early returnmap, and_then, or_else chains

warning

A panic in Rust is not like an exception — it is not meant to be caught in application code under normal circumstances. Use panic! for programming errors and bugs, not for expected failures like missing files or bad user input.
Traits & Generics

Traits define shared behavior, similar to interfaces in other languages. Generics let functions and structs operate over many types while maintaining compile-time type safety. Together they enable zero-cost, reusable abstractions.

traits.rs
RUST
1pub trait Summary {
2 fn summarize(&self) -> String;
3
4 fn default_summary(&self) -> String {
5 String::from("(Read more...)")
6 }
7}
8
9pub struct Article {
10 pub headline: String,
11 pub content: String,
12}
13
14impl Summary for Article {
15 fn summarize(&self) -> String {
16 format!("{} — {}", self.headline, &self.content[..20.min(self.content.len())])
17 }
18}
19
20pub fn notify(item: &impl Summary) {
21 println!("Breaking news! {}", item.summarize());
22}

Generic functions are monomorphized at compile time, meaning each concrete type gets its own optimized implementation. There is no runtime overhead from using generics compared to writing the same code by hand for each type.

generics.rs
RUST
1fn largest<T: PartialOrd>(list: &[T]) -> &T {
2 let mut largest = &list[0];
3
4 for item in list {
5 if item > largest {
6 largest = item;
7 }
8 }
9
10 largest
11}
12
13fn main() {
14 let numbers = vec![34, 50, 25, 100, 65];
15 println!("The largest number is {}", largest(&numbers));
16
17 let chars = vec!['y', 'm', 'a', 'q'];
18 println!("The largest char is {}", largest(&chars));
19}

info

Derive common traits such as Debug, Clone, Copy, PartialEq, and Default whenever possible. Manual implementations are verbose and easy to get wrong for simple data structures.
Complete Example: CLI Task Tracker

The following example ties together structs, enums, pattern matching, modules, and error handling into a small command-line application. It is intentionally simple but demonstrates idiomatic Rust structure.

task-tracker.rs
RUST
1// src/main.rs
2use std::collections::HashMap;
3
4#[derive(Debug, Clone)]
5struct Task {
6 id: u32,
7 description: String,
8 done: bool,
9}
10
11#[derive(Debug)]
12enum Command {
13 Add(String),
14 Complete(u32),
15 List,
16}
17
18struct TaskStore {
19 tasks: HashMap<u32, Task>,
20 next_id: u32,
21}
22
23impl TaskStore {
24 fn new() -> Self {
25 Self {
26 tasks: HashMap::new(),
27 next_id: 1,
28 }
29 }
30
31 fn add(&mut self, description: String) -> u32 {
32 let id = self.next_id;
33 self.next_id += 1;
34 self.tasks.insert(id, Task { id, description, done: false });
35 id
36 }
37
38 fn complete(&mut self, id: u32) -> Result<(), String> {
39 match self.tasks.get_mut(&id) {
40 Some(task) => {
41 task.done = true;
42 Ok(())
43 }
44 None => Err(format!("Task {id} not found")),
45 }
46 }
47
48 fn list(&self) {
49 for task in self.tasks.values() {
50 let status = if task.done { "[x]" } else { "[ ]" };
51 println!("{status} {}: {}", task.id, task.description);
52 }
53 }
54}
55
56fn parse_args(args: &[String]) -> Result<Command, String> {
57 if args.len() < 2 {
58 return Err("Usage: tasks <add|complete|list> [args]".to_string());
59 }
60
61 match args[1].as_str() {
62 "add" => {
63 let desc = args[2..].join(" ");
64 if desc.is_empty() {
65 return Err("Task description cannot be empty".to_string());
66 }
67 Ok(Command::Add(desc))
68 }
69 "complete" => {
70 let id: u32 = args.get(2)
71 .ok_or("Missing task id")?
72 .parse()
73 .map_err(|_| "Invalid task id")?;
74 Ok(Command::Complete(id))
75 }
76 "list" => Ok(Command::List),
77 other => Err(format!("Unknown command: {other}")),
78 }
79}
80
81fn main() {
82 let args: Vec<String> = std::env::args().collect();
83 let mut store = TaskStore::new();
84
85 match parse_args(&args) {
86 Ok(Command::Add(desc)) => {
87 let id = store.add(desc);
88 println!("Added task {id}");
89 }
90 Ok(Command::Complete(id)) => {
91 if let Err(e) = store.complete(id) {
92 eprintln!("Error: {e}");
93 std::process::exit(1);
94 }
95 println!("Completed task {id}");
96 }
97 Ok(Command::List) => store.list(),
98 Err(e) => {
99 eprintln!("Error: {e}");
100 std::process::exit(1);
101 }
102 }
103}
Common Pitfalls

Most Rust beginners fight the borrow checker at first. Understanding a few recurring mistakes will save hours of debugging compiler errors that ultimately point to real bugs.

PitfallWhy It HappensSolution
Fighting the borrow checkerTrying to hold references while mutating dataRestructure scope, clone, or use indices/RC where appropriate
unwrap everywherePanics at runtime instead of handling errorsUse ?, match, or combinators
String vs &str confusionOwned vs borrowed string typesUse &str for function parameters; String for owned data
Ignoring Result from fallible callsRust warns but does not stop compilation without #[must_use]Handle or explicitly discard with let _ = ...
Self-referential structsCannot safely hold references to own fieldsUse indices, Rc/Arc, or crates like ouroboros
Blocking in async codeSynchronous I/O stalls the runtimeUse async-aware APIs or spawn_blocking

danger

Do not use unsafe to silence the borrow checker until you deeply understand Rust's safety guarantees. Unsafe Rust is still checked for correctness by you, not the compiler, and a single mistake can reintroduce the memory bugs Rust is designed to prevent.
Ecosystem Overview

Rust's ecosystem is centered on crates.io, the package registry. Cargo makes it trivial to add dependencies, and documentation is generated automatically from doc comments. The language has strong communities around web servers, WebAssembly, embedded systems, and CLI tools.

DomainPopular CratesUse Case
Async runtimetokio, async-std, smolNetwork servers, async I/O
Web frameworksaxum, actix-web, rocketHTTP APIs and services
Serializationserde, serde_json, tomlJSON, YAML, TOML, binary formats
CLIclap, anyhow, thiserrorArgument parsing and error reporting
WebAssemblywasm-bindgen, trunk, leptosBrowser apps and WASI modules
Embeddedembedded-hal, defmt, probe-rsMicrocontrollers and no-std targets
Testingtokio-test, mockall, instaAsync tests, mocking, snapshot testing

Documentation is written with triple-slash comments and supports Markdown. Run cargo doc --open to browse locally generated docs. Writing good doc comments is a habit that pays off immediately in Rust because the tooling surfaces them so well.

doc-comments.rs
RUST
1/// Returns the squared Euclidean distance between two points.
2///
3/// # Examples
4///
5/// ```
6/// let d = forgelearn_demo::distance(0.0, 0.0, 3.0, 4.0);
7/// assert!((d - 25.0).abs() < f64::EPSILON);
8/// ```
9pub fn distance(x1: f64, y1: f64, x2: f64, y2: f64) -> f64 {
10 let dx = x2 - x1;
11 let dy = y2 - y1;
12 dx * dx + dy * dy
13}
Recommended Workflow

A productive Rust development loop combines Cargo's built-in commands with a few standard tools. Running checks frequently keeps feedback fast and catches borrow-checker issues before they spread.

rust-workflow.sh
Bash
1# Fast feedback loop during development
2cargo check # type-check only
3cargo clippy # lint and idiomatic suggestions
4cargo fmt # format code
5cargo test # run tests
6cargo build --release # optimized release build
7
8# IDE support
9rustup component add rust-analyzer # or use the rust-analyzer extension
10rustup component add rust-src

best practice

Add a CI job that runs cargo clippy -- -D warnings, cargo fmt --check, and cargo test. Enforcing these checks in CI is the easiest way to keep a Rust codebase consistent as it grows.
$Blueprint — Engineering Documentation·Section ID: RUST-OVERVIEW·Revision: 1.0

Community

Get help on Slack, Discord or VIP

Stuck on a guide? Join the community and ask.