Rust — Overview
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
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.
| Feature | Benefit | Example Use Case |
|---|---|---|
| Memory safety | No dangling pointers, use-after-free, or double-free bugs | Browsers, operating systems, embedded firmware |
| Zero-cost abstractions | High-level features compile to efficient machine code | Game engines, simulations, high-frequency data pipelines |
| Fearless concurrency | Data races caught at compile time | Web servers, async runtimes, parallel compute |
| Pattern matching | Exhaustive, expressive branching | Parsers, state machines, compilers |
| Cargo | Build, test, and dependency management in one tool | Every Rust project, from scripts to large applications |
| Cross-compilation | Build for many targets from one host | WebAssembly, embedded ARM, cloud containers |
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.
| 1 | # macOS, Linux, and WSL |
| 2 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh |
| 3 | |
| 4 | # Follow the prompts, then reload your shell |
| 5 | source "$HOME/.cargo/env" |
| 6 | |
| 7 | # Verify the installation |
| 8 | rustc --version |
| 9 | cargo --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.
| 1 | # Install an additional target, e.g. WebAssembly |
| 2 | rustup target add wasm32-unknown-unknown |
| 3 | |
| 4 | # Update to the latest stable toolchain |
| 5 | rustup update stable |
| 6 | |
| 7 | # Show installed toolchains |
| 8 | rustup toolchain list |
warning
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.
| 1 | // main.rs |
| 2 | fn main() { |
| 3 | println!("Hello, ForgeLearn!"); |
| 4 | } |
| 1 | # Compile and run directly |
| 2 | rustc main.rs |
| 3 | ./main |
| 4 | |
| 5 | # Or with Cargo |
| 6 | cargo new hello --bin |
| 7 | cd hello |
| 8 | cargo 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.
| 1 | fn 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
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.
| 1 | [package] |
| 2 | name = "forgelearn-demo" |
| 3 | version = "0.1.0" |
| 4 | edition = "2021" |
| 5 | authors = ["ForgeLearn <team@forgelearn.dev>"] |
| 6 | description = "A demo Rust crate for learning" |
| 7 | license = "MIT" |
| 8 | |
| 9 | [dependencies] |
| 10 | serde = { version = "1.0", features = ["derive"] } |
| 11 | tokio = { version = "1", features = ["full"] } |
| 12 | |
| 13 | [dev-dependencies] |
| 14 | assert_cmd = "2" |
| Command | Purpose | Output |
|---|---|---|
| cargo new NAME | Create a new binary crate | A directory with src/main.rs and Cargo.toml |
| cargo build | Compile the project | Debug binary in target/debug/ |
| cargo run | Build and execute the binary | Compiled output and program stdout |
| cargo test | Run unit and integration tests | Test report with pass/fail counts |
| cargo check | Type-check without producing a binary | Faster feedback during development |
| cargo clippy | Run the linter | Warnings about idiomatic issues |
| cargo doc --open | Generate and open documentation | Local 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.
| 1 | // src/lib.rs — library crate root |
| 2 | pub fn greet(name: &str) -> String { |
| 3 | format!("Hello, {name}!") |
| 4 | } |
| 5 | |
| 6 | #[cfg(test)] |
| 7 | mod tests { |
| 8 | use super::*; |
| 9 | |
| 10 | #[test] |
| 11 | fn test_greet() { |
| 12 | assert_eq!(greet("Rust"), "Hello, Rust!"); |
| 13 | } |
| 14 | } |
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.
| 1 | fn 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.
| Rule | Meaning | Compiler Error If Broken |
|---|---|---|
| One owner | A value is owned by exactly one binding | Use of moved value |
| Drop on scope exit | Destructor runs automatically at end of scope | Resource leaks are prevented by type system |
| Move by default | Assigning a heap value transfers ownership | Borrow 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.
| 1 | fn 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 | |
| 15 | fn takes_ownership(value: String) { |
| 16 | println!("{value}"); |
| 17 | } // value dropped here |
| 18 | |
| 19 | fn makes_copy(value: i32) { |
| 20 | println!("{value}"); |
| 21 | } |
warning
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.
| 1 | fn main() { |
| 2 | let s = String::from("borrowed"); |
| 3 | let len = calculate_length(&s); |
| 4 | |
| 5 | println!("'{s}' has length {len}"); |
| 6 | } |
| 7 | |
| 8 | fn 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.
| Rule | Example | Why It Matters |
|---|---|---|
| One mutable OR many immutable | Cannot borrow mutably while immutably borrowed | Prevents readers from observing torn writes |
| References must be valid | Cannot return a reference to a local variable | Eliminates dangling pointers |
| Mutable borrows are exclusive | Two &mut to the same data are forbidden | Guarantees no data races at compile time |
| 1 | fn 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 | |
| 14 | fn change(value: &mut String) { |
| 15 | value.push_str(", world"); |
| 16 | } |
best practice
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.
| 1 | fn 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 | |
| 13 | fn first_word(s: &str) -> &str { |
| 14 | match s.find(' ') { |
| 15 | Some(idx) => &s[..idx], |
| 16 | None => s, |
| 17 | } |
| 18 | } |
info
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.
| 1 | struct User { |
| 2 | username: String, |
| 3 | email: String, |
| 4 | active: bool, |
| 5 | sign_in_count: u64, |
| 6 | } |
| 7 | |
| 8 | fn 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.
| 1 | struct Point(i32, i32, i32); |
| 2 | struct AlwaysEqual; |
| 3 | |
| 4 | impl 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 | |
| 19 | fn 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
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.
| 1 | enum Message { |
| 2 | Quit, |
| 3 | Move { x: i32, y: i32 }, |
| 4 | Write(String), |
| 5 | ChangeColor(u8, u8, u8), |
| 6 | } |
| 7 | |
| 8 | fn main() { |
| 9 | let msg = Message::Move { x: 10, y: 20 }; |
| 10 | describe(&msg); |
| 11 | } |
| 12 | |
| 13 | fn 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.
| 1 | fn 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 | } |
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.
| 1 | enum Coin { |
| 2 | Penny, |
| 3 | Nickel, |
| 4 | Dime, |
| 5 | Quarter(String), // state name |
| 6 | } |
| 7 | |
| 8 | fn 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.
| 1 | fn 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
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.
| 1 | // src/lib.rs |
| 2 | mod 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 | |
| 14 | pub fn run() { |
| 15 | network::connect(); |
| 16 | network::client::ping(); |
| 17 | } |
| 18 | |
| 19 | // main.rs or another crate |
| 20 | use mycrate::run; |
| 21 | |
| 22 | fn 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.
| 1 | // src/front_of_house.rs |
| 2 | pub mod hosting; |
| 3 | |
| 4 | // src/front_of_house/hosting.rs |
| 5 | pub fn add_to_waitlist() {} |
| 6 | |
| 7 | // src/lib.rs |
| 8 | mod front_of_house; |
| 9 | |
| 10 | pub use front_of_house::hosting::add_to_waitlist; |
| 11 | |
| 12 | pub fn eat_at_restaurant() { |
| 13 | add_to_waitlist(); |
| 14 | } |
info
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.
| 1 | use std::fs::File; |
| 2 | use std::io::{self, Read}; |
| 3 | |
| 4 | fn 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 | |
| 11 | fn 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.
| 1 | fn last_char_of_first_line(text: &str) -> Option<char> { |
| 2 | text.lines().next()?.chars().last() |
| 3 | } |
| 4 | |
| 5 | fn main() { |
| 6 | let input = "hello |
| 7 | world"; |
| 8 | println!("{:?}", last_char_of_first_line(input)); |
| 9 | } |
| Approach | When to Use | Example |
|---|---|---|
| unwrap / expect | Tests, prototypes, truly invariant cases | Hardcoded config that must exist |
| match | When every branch needs distinct logic | Custom retry or fallback behavior |
| if let / while let | Single variant handling | Optional logging or extraction |
| ? | Error propagation in fallible functions | I/O or parsing helper functions |
| Result combinators | Functional composition without early return | map, and_then, or_else chains |
warning
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.
| 1 | pub trait Summary { |
| 2 | fn summarize(&self) -> String; |
| 3 | |
| 4 | fn default_summary(&self) -> String { |
| 5 | String::from("(Read more...)") |
| 6 | } |
| 7 | } |
| 8 | |
| 9 | pub struct Article { |
| 10 | pub headline: String, |
| 11 | pub content: String, |
| 12 | } |
| 13 | |
| 14 | impl Summary for Article { |
| 15 | fn summarize(&self) -> String { |
| 16 | format!("{} — {}", self.headline, &self.content[..20.min(self.content.len())]) |
| 17 | } |
| 18 | } |
| 19 | |
| 20 | pub 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.
| 1 | fn 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 | |
| 13 | fn 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
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.
| 1 | // src/main.rs |
| 2 | use std::collections::HashMap; |
| 3 | |
| 4 | #[derive(Debug, Clone)] |
| 5 | struct Task { |
| 6 | id: u32, |
| 7 | description: String, |
| 8 | done: bool, |
| 9 | } |
| 10 | |
| 11 | #[derive(Debug)] |
| 12 | enum Command { |
| 13 | Add(String), |
| 14 | Complete(u32), |
| 15 | List, |
| 16 | } |
| 17 | |
| 18 | struct TaskStore { |
| 19 | tasks: HashMap<u32, Task>, |
| 20 | next_id: u32, |
| 21 | } |
| 22 | |
| 23 | impl 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 | |
| 56 | fn 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 | |
| 81 | fn 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 | } |
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.
| Pitfall | Why It Happens | Solution |
|---|---|---|
| Fighting the borrow checker | Trying to hold references while mutating data | Restructure scope, clone, or use indices/RC where appropriate |
| unwrap everywhere | Panics at runtime instead of handling errors | Use ?, match, or combinators |
| String vs &str confusion | Owned vs borrowed string types | Use &str for function parameters; String for owned data |
| Ignoring Result from fallible calls | Rust warns but does not stop compilation without #[must_use] | Handle or explicitly discard with let _ = ... |
| Self-referential structs | Cannot safely hold references to own fields | Use indices, Rc/Arc, or crates like ouroboros |
| Blocking in async code | Synchronous I/O stalls the runtime | Use async-aware APIs or spawn_blocking |
danger
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.
| Domain | Popular Crates | Use Case |
|---|---|---|
| Async runtime | tokio, async-std, smol | Network servers, async I/O |
| Web frameworks | axum, actix-web, rocket | HTTP APIs and services |
| Serialization | serde, serde_json, toml | JSON, YAML, TOML, binary formats |
| CLI | clap, anyhow, thiserror | Argument parsing and error reporting |
| WebAssembly | wasm-bindgen, trunk, leptos | Browser apps and WASI modules |
| Embedded | embedded-hal, defmt, probe-rs | Microcontrollers and no-std targets |
| Testing | tokio-test, mockall, insta | Async 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.
| 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 | /// ``` |
| 9 | pub 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 | } |
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.
| 1 | # Fast feedback loop during development |
| 2 | cargo check # type-check only |
| 3 | cargo clippy # lint and idiomatic suggestions |
| 4 | cargo fmt # format code |
| 5 | cargo test # run tests |
| 6 | cargo build --release # optimized release build |
| 7 | |
| 8 | # IDE support |
| 9 | rustup component add rust-analyzer # or use the rust-analyzer extension |
| 10 | rustup component add rust-src |
best practice
Community
Get help on Slack, Discord or VIP
Stuck on a guide? Join the community and ask.