Developed by Graydon Hoare at Mozilla Research in 2010.
Designed to provide memory safety, concurrency, and performance without the pitfalls of C and C++.
Focuses on ensuring memory safety without a garbage collector using ownership and borrowing rules.
Stable release of Rust 1.0 was in 2015. Evolved with async/await in 1.39 and const generics in 1.51.
Maintained today by the independent, non-profit Rust Foundation.
Who:
Graydon Hoare (creator) and Mozilla Research.
Rust Foundation (established in 2021 by Mozilla, AWS, Google, Huawei, Microsoft).
Why:
To replace C/C++ in performance-critical, security-sensitive applications (browsers, OS, kernels, database engines).
To eliminate memory corruption bugs (use-after-free, double free, buffer overflow) at compile time.
To make concurrent programming safe and free of data races.
Introduction
Core Pillars
Memory Safety Without GC — The compiler’s borrow checker validates references at compile time, eliminating the runtime overhead of a garbage collector and the safety hazards of manual allocation.
Fearless Concurrency — Type safety and ownership rules prevent data races (two threads accessing the same memory concurrently, with at least one writer) at compile time.
Zero-Cost Abstractions — High-level constructs (iterators, closures, pattern matching, generics) compile down to low-level assembly identical to hand-optimized C/C++.
Advantages
Absolute Safety — Zero segment faults, null pointer dereferences, or buffer overflows in safe Rust.
Predictable Performance — Minimal runtime, no garbage collection pauses, direct hardware control.
Modern Tooling — Cargo handles compiling, dependencies, testing, formatting, linting, and documentation out of the box.
Algebraic Data Types — Enums with associated data combined with pattern matching prevent invalid states.
Expressive Type System — Traits define shared behavior clearly, facilitating reusable generic components.
Disadvantages
Borrow Checker Friction — The compiler can be highly restrictive, especially for developers transitioning from garbage-collected languages or those writing cyclic data structures (like graphs).
Compile Times — Strict safety checks, monomorphization of generics, and advanced optimizations make compile times slower than Go or C.
No Traditional OOP — Lacks class-based inheritance; reuse is achieved via composition, traits, and generics, which requires a mindset shift.
Syntax Verbosity — Lifetime annotations ('a) and nested generic constraints can make signatures complex and hard to read.
Basics
Hello World & Entry Point
fn main() { println!("Hello, World!");}
fn main() is the entry point of every executable Rust program.
println! is a macro (indicated by the !), which performs compile-time string format validation.
Comments
// Single-line comment/* Multi-line comment block *//// Outer doc comment (generates documentation for the item below it)//! Inner doc comment (generates documentation for the enclosing module/crate)
Variables, Mutability & Shadowing
// Variables are immutable by defaultlet x = 5; // x = 6; // Compile Error!// Use 'mut' to make variables mutablelet mut y = 10;y = 15; // Allowed// Constants must have explicit types and are evaluated at compile timeconst MAX_POINTS: u32 = 100_000;// Shadowing: Re-declaring a variable with the same name in the same scopelet spaces = " ";let spaces = spaces.len(); // Shadowing allows changing type and mutability
In Rust, almost everything is an expression (evaluates to a value). Statements end in semicolons and do not return values (or rather, return the unit type ()).
// Block expression returning a valuelet y = { let x = 3; x + 1 // No semicolon means this is the return expression}; // y = 4// Arithmetic, Relational, Logical, and Bitwise operators are standard:// +, -, *, /, %, ==, !=, <, >, <=, >=, &&, ||, !, &, |, ^, <<, >>
Control Flow
if / else expressions
let number = 6;if number % 4 == 0 { println!("divisible by 4");} else if number % 3 == 0 { println!("divisible by 3");} else { println!("not divisible by 4 or 3");}// 'if' is an expression and can bind to a variablelet condition = true;let val = if condition { 5 } else { 6 }; // arms must return the same type
Loops: loop, while, and for
// loop: infinite loop with breaks returning valueslet mut counter = 0;let result = loop { counter += 1; if counter == 10 { break counter * 2; // Returns 20 }};// Loop labels for nested loops'outer: loop { loop { break 'outer; // Exits the outer loop }}// while: conditional looplet mut number = 3;while number != 0 { number -= 1;}// for: iterator-based loop (safe and optimized)let a = [10, 20, 30, 40, 50];for element in a.iter() { println!("value: {}", element);}// range loops (exclusive: 1..4 prints 1, 2, 3; inclusive: 1..=4 prints 1, 2, 3, 4)for number in (1..4).rev() { println!("{}!", number);}
// Type annotations are mandatory for parameters and return typesfn add(x: i32, y: i32) -> i32 { x + y // Implicit return (expression, no semicolon)}// Early returns use the 'return' keywordfn check_even(x: i32) -> bool { if x % 2 == 0 { return true; } false}
Closures (Anonymous Functions)
// Basic closure syntax: |param1, param2| expressionlet add_one = |x: i32| x + 1;let result = add_one(5); // 6// Type inference allows omitting type annotationslet print_sum = |x, y| println!("sum: {}", x + y);// Capture modes: closures automatically borrow or take ownership of variableslet list = vec![1, 2, 3];// Borrowing immutablylet only_borrows = || println!("From list: {:?}", list);// Forcing ownership transfer using the 'move' keywordlet takes_ownership = move || println!("Owned list: {:?}", list);// println!("{:?}", list); // Compile Error! list was moved into closure
Fn, FnMut, and FnOnce Traits
Rust classifies closures into three traits based on how they handle captured variables:
FnOnce — Consumes captures. Can be called at most once (moves out of captures).
FnMut — Mutates captures. Can be called multiple times (borrows mutably).
Fn — Borrows captures immutably. Can be called concurrently and multiple times.
fn call_once<F>(f: F) where F: FnOnce() { f(); }fn call_mut<F>(mut f: F) where F: FnMut() { f(); }fn call_shared<F>(f: F) where F: Fn() { f(); }
Ownership, Borrowing & Lifetimes
The 3 Rules of Ownership
Each value in Rust has an owner (variable).
There can only be one owner at a time.
When the owner goes out of scope, the value is automatically dropped (memory freed).
Move vs Copy Semantics
If a type implements the Copy trait (e.g., primitive types stored on stack), assignment performs a bitwise copy.
Otherwise, assignment/passing transfers ownership (Move), and the source variable becomes invalid.
// Move Semanticslet s1 = String::from("hello"); // Heap allocatedlet s2 = s1; // Ownership is moved to s2. s1 is now invalid!// println!("{}", s1); // Compile Error!// Copy Semanticslet x = 5;let y = x; // x is Copy, so both x and y are valid and independent.println!("x: {}, y: {}", x, y); // Works// Explicit Deep Copyinglet s3 = s2.clone(); // Deep copy of heap data. Both s2 and s3 are valid.
Borrowing and References
To use a value without taking ownership, we borrow it using References (&).
fn calculate_length(s: &String) -> usize { // Borrowed reference s.len()} // s goes out of scope, but since it doesn't own what it points to, nothing happens.
The Borrow Checker Rules
At any given time, you can have:
Rule 1: Either any number of immutable references (&T) to a resource…
Rule 2: OR exactly one mutable reference (&mut T) to a resource.
Rule 3: References must always be valid (never point to dropped memory - no dangling references).
let mut s = String::from("hello");let r1 = &s; // Finelet r2 = &s; // Fine// let r3 = &mut s; // Compile Error! Cannot borrow mutably when immutably borrowed.println!("{}, {}", r1, r2); // r1 and r2 scopes end after their last uselet r3 = &mut s; // Fine! r1 and r2 are no longer active in scope.
Lifetimes: Preventing Dangling References
Lifetimes are named regions of scope that the compiler (borrow checker) uses to verify that all borrows are valid.
// Function with generic lifetime parameter 'a// Specifies that the returned reference lives as long as the shortest of 'x' or 'y'fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y }}
Lifetime Elision Rules
The compiler automatically deduces lifetimes in function signatures based on three rules:
Each input parameter gets its own input lifetime parameter (e.g., fn foo<'a, 'b>(x: &'a i32, y: &'b i32)).
If there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters (e.g., fn foo<'a>(x: &'a i32) -> &'a i32).
If there are multiple input lifetime parameters, but one of them is &self or &mut self, the lifetime of self is assigned to all outputs.
Lifetimes in Structs
If a struct holds a reference, it must be annotated with a lifetime, indicating the struct cannot outlive the reference it holds.
// Match must be exhaustivelet some_u8_value = Some(3u8);match some_u8_value { Some(1) => println!("one"), Some(3) => println!("three"), _ => (), // Wildcard placeholder for other cases}// if let shorthand (when you only care about one pattern)if let Some(3) = some_u8_value { println!("Found three!");}// while let conditional looplet mut stack = vec![1, 2, 3];while let Some(top) = stack.pop() { println!("{}", top);}
Advanced Pattern Matching Patterns
struct Point { x: i32, y: i32 }let p = Point { x: 0, y: 7 };// Destructuring and match guardsmatch p { Point { x, y } if x == y => println!("On diagonal at {}", x), Point { x: 0, y } => println!("On Y axis at {}", y), Point { x, y: 0 } => println!("On X axis at {}", x), Point { x, y } => println!("Somewhere else at ({}, {})", x, y),}// @ Binding: capture a value while testing itenum Message { Hello { id: i32 } }let msg = Message::Hello { id: 5 };match msg { Message::Hello { id: id_variable @ 3..=7 } => { println!("Found an id in range 3 to 7: {}", id_variable) } Message::Hello { id: 10..=12 } => println!("Found id in range 10-12"), _ => ()}
Traits & Generics
Generics
// Generic Structstruct Point<T> { x: T, y: T,}// Generic Functionfn largest<T: PartialOrd>(list: &[T]) -> &T { let mut largest = &list[0]; for item in list { if item > largest { largest = item; } } largest}
Drop: Destructor logic for releasing resources (RAII). Cannot implement both Copy and Drop.
Iterator: Allows iteration over sequences (requires next() method implementation).
Display: Custom user-facing formatting with {}.
The Orphan Rule
You can implement a trait on a type if and only if either the trait or the type is local to your crate. This prevents third-party crates from breaking each other by defining conflicting implementations for external types (e.g., implementing Display on Vec<T> is forbidden).
Memory Management & Smart Pointers
RAII (Resource Acquisition Is Initialization)
Resources (memory, files, sockets) are acquired in constructors and released automatically inside the Drop::drop destructor when variables go out of scope.
struct CustomSmartPointer { data: String,}impl Drop for CustomSmartPointer { fn drop(&mut self) { println!("Dropping CustomSmartPointer with data `{}`!", self.data); }}
Box (Heap Allocation)
Used to store data on the heap instead of the stack. Provides exclusive ownership.
let b = Box::new(5); // Stores '5' on the heap, 'b' points to itprintln!("b = {}", *b); // Dereferencing to access value
Rc (Reference Counting)
Enables shared ownership within a single thread. Tracks the number of references. When reference count drops to 0, data is cleaned up.
Thread-safe version of Rc<T> using atomic operations. Essential for multi-threaded shared ownership, but has a slight performance overhead compared to Rc.
use std::sync::Arc;use std::thread;let data = Arc::new(vec![1, 2, 3]);let data_clone = Arc::clone(&data);thread::spawn(move || { println!("From thread: {:?}", data_clone);}).join().unwrap();
Cell and RefCell (Interior Mutability)
Allows mutating values even when the wrapping container is immutable. Borrows rules are enforced at runtime rather than compile time.
Cell<T>: Works on Copy types by copying values in/out. No borrowing overhead.
RefCell<T>: Works on non-copy types, returning dynamically checked smart references (Ref and RefMut). Panics at runtime if borrowing rules are violated.
use std::cell::RefCell;let val = RefCell::new(5);// Mutate even though 'val' is immutable*val.borrow_mut() += 10; println!("val: {:?}", val.borrow()); // 15// Runtime Panic example:let r1 = val.borrow();// let r2 = val.borrow_mut(); // Panic at runtime! Already borrowed immutably.
Smart Pointer Comparison Table
Smart Pointer Ownership Threading Borrow Check Timing
Box<T> Exclusive N/A Compile Time
Rc<T> Shared Single-Thread Compile Time
Arc<T> Shared Multi-Thread Compile Time
RefCell<T> Interior Single-Thread Runtime
Mutex<T> Interior/Lock Multi-Thread Runtime
Concurrency
Thread Spawning
use std::thread;use std::time::Duration;let handle = thread::spawn(|| { for i in 1..5 { println!("hi number {} from the spawned thread!", i); thread::sleep(Duration::from_millis(1)); }});handle.join().unwrap(); // Wait for thread to finish
Message Passing (Channels)
Multi-Producer, Single-Consumer (mpsc) channel for passing data between threads safely.
use std::sync::mpsc;use std::thread;let (tx, rx) = mpsc::channel();thread::spawn(move || { let val = String::from("message from thread"); tx.send(val).unwrap(); // transfers ownership of 'val' to receiver thread});let received = rx.recv().unwrap(); // blocks main thread until message arrivesprintln!("Got: {}", received);
Shared State (Mutex & Arc)
Safely mutates data across multiple threads by acquiring locks.
use std::sync::{Arc, Mutex};use std::thread;let counter = Arc::new(Mutex::new(0));let mut handles = vec![];for _ in 0..10 { let counter = Arc::clone(&counter); let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); // Acquires lock, blocks until free *num += 1; }); handles.push(handle);}for handle in handles { handle.join().unwrap();}println!("Result: {}", *counter.lock().unwrap()); // 10
Send & Sync (Thread Safety Auto-Traits)
Rust enforces thread safety statically using two marker traits:
Send: Indicates ownership of the type can be transferred across thread boundaries. Most types are Send (except Rc<T>, raw pointers).
Sync: Indicates it is safe for multiple threads to access references of this type concurrently. A type T is Sync if and only if &T is Send (e.g. types with raw interior mutability like RefCell are not Sync, whereas Mutex is).
Metaprogramming (Macros)
Declarative Macros (macro_rules!)
Pattern-matching macros that generate code at compile time.
An unsafe block disables compiler safety checks for five specific operations (“superpowers”). Regular borrow checker rules inside unsafe are still active, but the programmer takes full responsibility.
Dereferencing raw pointers.
Calling unsafe functions/methods.
Implementing unsafe traits.
Mutating static mutable variables.
Accessing fields of a union.
Dereferencing Raw Pointers
Raw pointers (*const T, *mut T) bypass the borrow checker. Creating them is safe, but dereferencing them must be inside an unsafe block.
let mut num = 5;// Safe raw pointer creationlet r1 = &num as *const i32;let r2 = &mut num as *mut i32;// Unsafe dereferencingunsafe { println!("r1 is: {}", *r1); *r2 = 10; println!("r2 is: {}", *r2);}
Calling Unsafe Functions and FFI
// Unsafe function definitionunsafe fn dangerous() {}unsafe { dangerous(); // Must be called within unsafe}// Foreign Function Interface (FFI) - calling C codeextern "C" { fn abs(input: i32) -> i32;}fn main() { unsafe { println!("Absolute value of -3 according to C: {}", abs(-3)); }}
Mutating Static Mutable Variables
Global mutable state is inherently unsafe because of potential data races.
cargo new my_project # Create new packagecargo build # Compile debug binary (target/debug/)cargo build --release # Compile optimized production binary (target/release/)cargo run # Build and execute the program in one stepcargo check # Perform compile check (fast, no binary creation)cargo test # Run unit and integration testscargo doc --open # Generate and display HTML documentation
Config files: Cargo.toml
[package]name = "my_project"version = "0.1.0"edition = "2021"[dependencies]serde = { version = "1.0", features = ["derive"] }tokio = { version = "1.0", features = ["full"] }
Standard Quality Tools
rustc — The Rust compiler.
rustup — Handles toolchains (switch between stable, beta, nightly) and updates.
clippy — A powerful collection of lints to catch common mistakes and enforce best idioms. Run using cargo clippy.
rustfmt — Standard automatic formatter to keep codebase styles clean. Run using cargo fmt.
Comparison: Rust vs C++
Direct Comparison Table
Feature Rust C++
Memory Management Borrow Checker + RAII (Zero GC) Smart Pointers / Manual RAII / Raw delete
Type & Memory Safety Guaranteed at compile time (Safe Rust) Manual, prone to Undefined Behavior (UB)
Generics Traits constraints (Monomorphization) Templates (Monomorphization) / Concepts
OOP Paradigm Composition over inheritance (Traits) Traditional class-based inheritance
Error Handling Result<T, E> & Option<T> (No Exceptions) try/catch Exceptions or return codes
Concurrency Safety Statically guaranteed (Send/Sync) Manual locking, data races are undefined
Package Management Cargo (Standardized, Crates.io) Varies (Vcpkg, Conan, CMake, manual)
Compile Times Typically slow (strict static checks) Varies, slow on templates instantiation
Metadata & Reflection Proc-Macros (AST analysis) Limited RTTI / Templates tricks
Libraries & Frameworks
Core Libraries and Frameworks:
Rust Standard Library - Essential functionality such as file I/O, networking, collections, and concurrency utilities.
Cargo - Rust’s package manager and build system, making it easy to manage dependencies, compile code, and distribute packages.
Serde - A powerful serialization/deserialization library for Rust, commonly used for working with JSON, TOML, YAML, and other data formats.
Web Development Frameworks:
Rocket - A web framework for Rust that focuses on ease of use, type safety, and speed. Rocket provides high-level abstractions for routing, request handling, and templating.
Actix - A powerful actor-based framework for building fast and reliable web applications in Rust, offering a highly concurrent and efficient model for handling requests.
Warp - A lightweight and fast web framework based on tokio and hyper, designed for building asynchronous, highly concurrent web services.
Tide - An async-first, minimalist web framework built on async-std, designed to be simple and extendable for web application development.
Database and Data Management:
Diesel - A popular ORM for Rust, offering type safety for SQL queries and support for PostgreSQL, SQLite, and MySQL databases.
SQLx - A database library for asynchronous SQL queries in Rust, supporting MySQL, PostgreSQL, and SQLite.
MongoDB Rust Driver - The official Rust driver for MongoDB, enabling efficient interaction with MongoDB databases.
Testing:
Rust’s built-in testing - Rust includes a built-in testing framework, enabling unit tests, integration tests, and documentation tests as part of the standard development process.
Cargo test - A command within Cargo to run unit tests and integration tests, making it easy to verify the correctness of your codebase.
Proptest - A property-based testing library for Rust that generates test cases based on properties of the code rather than individual examples.
Concurrency:
Tokio - A runtime for building asynchronous applications in Rust, providing a powerful set of tools for handling asynchronous I/O, networking, and concurrency.
async-std - An alternative asynchronous runtime for Rust, designed to provide an async version of the Rust standard library.
Rayon - A data parallelism library for Rust that simplifies multi-threading by providing a high-level API for parallel iteration over collections.
Networking:
Hyper - A fast and low-level HTTP library for Rust, offering both client and server-side support for HTTP/1 and HTTP/2.
Reqwest - An HTTP client library built on top of hyper, designed to provide a simpler, high-level interface for making HTTP requests.
Mio - A low-level, event-driven I/O library for Rust, providing building blocks for asynchronous networking applications.
Cryptography and Security:
RustCrypto - A collection of cryptographic algorithms and utilities written in Rust, including hashing algorithms, block ciphers, and elliptic curve operations.
Sodiumoxide - A Rust wrapper for the libsodium cryptographic library, offering modern cryptographic primitives and secure encryption algorithms.
OpenSSL - Rust bindings for the OpenSSL library, enabling access to SSL/TLS encryption and cryptographic functionality.
Logging:
log - A logging facade for Rust, providing a unified interface for logging that works with various logging implementations.
env_logger - A simple logger for Rust, designed to be used with the log crate, enabling logging based on environment variables.
Serialization and Data Formats:
Serde - A powerful framework for serializing and deserializing Rust data structures, used for working with JSON, TOML, YAML, and more.
Bincode - A binary serialization library for Rust, focusing on efficient, compact serialization with support for custom serialization strategies.
Miscellaneous:
Clippy - A linter for Rust that provides helpful suggestions to improve code quality, optimize performance, and ensure best practices.
Rustfmt - A tool for automatically formatting Rust code according to the official style guidelines.
More Learn
Explore the following links for valuable resources, communities, and tools to enhance your skills : -