The Complete Rust Language Guide
Master Rust — the language that guarantees memory safety without a garbage collector, enabling fearless concurrency and blazing performance.
Why Learn Rust?
Rust was created by Mozilla Research and released in 2010. It has been voted the "most loved programming language" in the Stack Overflow Developer Survey for eight consecutive years. Rust gives you the low-level control of C++ with the safety guarantees of modern languages.
Rust's ownership system enforces memory safety at compile time without needing a garbage collector. This means zero-cost abstractions, no null pointer dereferences, no data races, and no dangling references — all guaranteed before your code even runs.
Rust is used in WebAssembly, game engines, operating systems, embedded systems, and major platforms like Cloudflare, Discord, and the Linux kernel.
Core principle: In Rust, memory safety bugs are compile-time errors, not runtime crashes.
1. Variables & Data Types
Variables and Mutability
In Rust, variables are immutable by default. You must explicitly use 'mut' to allow mutation. This forces intentional design around state changes.
fn main() {
// Immutable by default
let x = 5;
// x = 6; // ERROR: cannot assign twice to immutable variable
// Mutable variable
let mut count = 0;
count += 1;
println!("Count: {}", count);
// Shadowing (rebind same name)
let spaces = " ";
let spaces = spaces.len(); // different type is fine
// Data types
let integer: i32 = 42;
let float: f64 = 3.14;
let boolean: bool = true;
let character: char = 'R';
let tuple: (i32, f64, bool) = (500, 6.4, true);
let array: [i32; 5] = [1, 2, 3, 4, 5];
println!("{} {} {} {} {}", integer, float, boolean, character, spaces);
println!("Tuple first: {}", tuple.0);
println!("Array third: {}", array[2]);
}Control Flow
Rust's if expressions can return values, making them usable in assignments. The loop, while, and for constructs cover all iteration needs.
fn main() {
// if as an expression
let number = 7;
let description = if number % 2 == 0 { "even" } else { "odd" };
println!("{} is {}", number, description);
// loop with return value
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // returns 20
}
};
println!("Result: {}", result);
// for with ranges
for i in 1..=5 {
println!("i = {}", i);
}
// iterating over a collection
let animals = ["cat", "dog", "bird"];
for animal in animals.iter() {
println!("Animal: {}", animal);
}
}2. Ownership & Borrowing
Ownership Rules
Every value has a single owner. When ownership is moved, the original variable is invalid. References allow borrowing without taking ownership.
fn main() {
// Move semantics
let s1 = String::from("hello");
let s2 = s1; // s1 is moved to s2
// println!("{}", s1); // ERROR: s1 is no longer valid
println!("{}", s2); // OK
// Clone to deep copy
let s3 = s2.clone();
println!("{} and {}", s2, s3); // both valid
// Borrowing with references
let s4 = String::from("world");
let len = calculate_length(&s4); // borrow, not move
println!("{} has length {}", s4, len); // s4 still valid
// Mutable reference
let mut s5 = String::from("hello");
change(&mut s5);
println!("{}", s5);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
fn change(s: &mut String) {
s.push_str(", world");
}3. Structs & Enums
Structs and impl blocks
Structs bundle related data. Methods are defined in impl blocks. Enums in Rust are algebraic data types and can hold data in each variant.
#[derive(Debug)]
struct Rectangle {
width: f64,
height: f64,
}
impl Rectangle {
// Associated function (constructor)
fn new(width: f64, height: f64) -> Rectangle {
Rectangle { width, height }
}
// Method
fn area(&self) -> f64 {
self.width * self.height
}
fn is_square(&self) -> bool {
self.width == self.height
}
}
// Enum with data
enum Shape {
Circle(f64),
Rectangle(f64, f64),
Triangle(f64, f64, f64),
}
impl Shape {
fn area(&self) -> f64 {
match self {
Shape::Circle(r) => std::f64::consts::PI * r * r,
Shape::Rectangle(w, h) => w * h,
Shape::Triangle(a, b, c) => {
let s = (a + b + c) / 2.0;
(s * (s - a) * (s - b) * (s - c)).sqrt()
}
}
}
}
fn main() {
let rect = Rectangle::new(10.0, 5.0);
println!("Area: {}", rect.area());
println!("Square? {}", rect.is_square());
println!("{:?}", rect);
let circle = Shape::Circle(3.0);
println!("Circle area: {:.2}", circle.area());
}4. Traits & Generics
Defining and Implementing Traits
Traits define shared behavior, similar to interfaces. Generics let you write code that works across multiple types without duplication.
trait Greet {
fn greeting(&self) -> String;
fn greet(&self) {
println!("{}", self.greeting());
}
}
struct English;
struct Spanish;
impl Greet for English {
fn greeting(&self) -> String {
String::from("Hello!")
}
}
impl Greet for Spanish {
fn greeting(&self) -> String {
String::from("Hola!")
}
}
// Generic function with trait bound
fn print_greeting<T: Greet>(item: &T) {
item.greet();
}
// Generic struct
struct Pair<T> {
first: T,
second: T,
}
impl<T: std::fmt::Display> Pair<T> {
fn show(&self) {
println!("({}, {})", self.first, self.second);
}
}
fn main() {
print_greeting(&English);
print_greeting(&Spanish);
let pair = Pair { first: 10, second: 20 };
pair.show();
}5. Error Handling
Result and Option types
Rust has no null values and no exceptions. Instead, it uses Option<T> for potentially absent values and Result<T, E> for operations that can fail. The ? operator propagates errors elegantly.
use std::num::ParseIntError;
use std::fs;
// Option for nullable values
fn find_first_even(numbers: &[i32]) -> Option<i32> {
numbers.iter().find(|&&x| x % 2 == 0).copied()
}
// Result for fallible operations
fn parse_number(s: &str) -> Result<i32, ParseIntError> {
s.trim().parse::<i32>()
}
// ? operator for error propagation
fn read_and_parse(filename: &str) -> Result<i32, Box<dyn std::error::Error>> {
let content = fs::read_to_string(filename)?;
let number = content.trim().parse::<i32>()?;
Ok(number * 2)
}
fn main() {
// Option
let numbers = vec![1, 3, 5, 4, 7];
match find_first_even(&numbers) {
Some(n) => println!("First even: {}", n),
None => println!("No even numbers"),
}
// Result
match parse_number("42") {
Ok(n) => println!("Parsed: {}", n),
Err(e) => println!("Error: {}", e),
}
// if let shorthand
if let Ok(n) = parse_number("100") {
println!("Got number: {}", n);
}
}6. Fearless Concurrency
Threads and Message Passing
Rust prevents data races at compile time. Threads communicate via channels or share state with Mutex. The ownership system ensures safe concurrent access.
use std::sync::{Arc, Mutex};
use std::thread;
use std::sync::mpsc;
fn main() {
// Spawning threads
let handle = thread::spawn(|| {
for i in 1..5 {
println!("Thread: {}", i);
}
});
handle.join().unwrap();
// Message passing with channels
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let messages = vec!["hi", "from", "the", "thread"];
for msg in messages {
tx.send(msg).unwrap();
}
});
for received in rx {
println!("Got: {}", received);
}
// Shared state with Arc<Mutex<T>>
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..5 {
let c = Arc::clone(&counter);
let h = thread::spawn(move || {
let mut num = c.lock().unwrap();
*num += 1;
});
handles.push(h);
}
for h in handles { h.join().unwrap(); }
println!("Counter: {}", *counter.lock().unwrap());
}Keep Rusting!
Rust's learning curve is steep but the payoff is enormous — memory-safe, high-performance code that you can trust at scale.