Memory safe, blazing fast — 6 lessons covering ownership, traits, error handling, concurrency, and unsafe Rust. Build fearless concurrent systems.
Every value has exactly one owner. When the owner goes out of scope, the value is dropped. This eliminates garbage collectors, data races, and dangling pointers at compile time.
fn main() {
// Rule 1: Each value has exactly one owner
let s1 = String::from("hello");
// Rule 2: When the owner goes out of scope, the value is dropped
{
let s2 = String::from("inside block");
println!("{}", s2); // s2 is valid here
}
// s2 is dropped here — memory freed automatically
// Rule 3: Assignment transfers ownership (move)
let s3 = s1; // s1 is MOVED to s3
// println!("{}", s1); // ERROR: s1 is no longer valid!
println!("{}", s3); // s3 owns the data now
}
fn main() {
// Clone — deep copy of heap data
let s1 = String::from("hello");
let s2 = s1.clone(); // both s1 and s2 are valid
println!("s1 = {}, s2 = {}", s1, s2);
// Copy types — stack-only, trivially copyable
let x = 42;
let y = x; // Copy, not move
println!("x = {}, y = {}", x, y); // both valid!
// Types that implement Copy:
// i8, i16, i32, i64, i128, isize
// u8, u16, u32, u64, u128, usize
// f32, f64
// bool, char
// Tuples of Copy types: (i32, f64)
}
fn main() {
let s1 = String::from("hello");
// Immutable borrow — can have many simultaneously
let len = calculate_length(&s1);
println!("Length of '{}' is {}", s1, len);
// Mutable borrow — only ONE at a time
let mut s2 = String::from("hello");
change(&mut s2);
println!("Modified: {}", s2);
}
fn calculate_length(s: &String) -> usize {
s.len()
// s goes out of scope but doesn't drop the original
}
fn change(s: &mut String) {
s.push_str(", world");
}
// Lifetime annotations tell the compiler how references relate
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 { 3 }
fn announce_and_return(&self, announcement: &str) -> &str {
println!("Attention: {}", announcement);
self.part
}
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence;
{
let i = novel.split('.').next().expect("Could not find a '.'");
first_sentence = ImportantExcerpt { part: i };
}
println!("First sentence: {}", first_sentence.part);
}
fn main() {
// String — heap-allocated, growable, owned
let owned: String = String::from("I own this");
// &str — string slice, borrowed view into string data
let slice: &str = "I'm a string literal"; // lives in binary
let slice2: &str = &owned[..]; // borrow of String
// Conversions
let from_str: &str = "hello";
let to_string: String = from_str.to_string();
let from_string: String = String::from("hello");
let to_slice: &str = &from_string;
// Function signatures — prefer &str for input
fn greet(name: &str) {
println!("Hello, {}!", name);
}
greet(&to_string); // String coerces to &str
greet("literal"); // &str directly
}
&str in function parameters for flexibilitylet s2 = s1; where s1 is a String?struct User {
username: String,
email: String,
active: bool,
sign_in_count: u64,
}
fn main() {
// Create instance — all fields required
let user = User {
username: String::from("mayank"),
email: String::from("mayank@example.com"),
active: true,
sign_in_count: 1,
};
// Field init shorthand (variable name == field name)
let username = String::from("alice");
let email = String::from("alice@example.com");
let user2 = User {
username,
email,
active: true,
sign_in_count: 1,
};
// Struct update syntax
let user3 = User {
email: String::from("new@example.com"),
..user2 // remaining fields from user2
};
}
struct Rectangle {
width: f64,
height: f64,
}
impl Rectangle {
// Method — takes &self
fn area(&self) -> f64 {
self.width * self.height
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
// Associated function — no self (like static methods)
fn square(size: f64) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
fn main() {
let rect = Rectangle { width: 30.0, height: 50.0 };
println!("Area: {}", rect.area());
let sq = Rectangle::square(20.0);
println!("Square area: {}", sq.area());
}
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
impl Message {
fn call(&self) {
match self {
Message::Quit => println!("Quit"),
Message::Move { x, y } => println!("Move to ({}, {})", x, y),
Message::Write(text) => println!("Message: {}", text),
Message::ChangeColor(r, g, b) => println!("Color: ({}, {}, {})", r, g, b),
}
}
}
fn main() {
let home = IpAddr::V4(127, 0, 0, 1);
let msg = Message::Write(String::from("hello"));
msg.call();
}
// Option replaces null — compiler forces you to handle it
fn find_user(id: u32) -> Option<String> {
match id {
1 => Some(String::from("Mayank")),
_ => None,
}
}
fn main() {
let user = find_user(1);
// match on Option
match &user {
Some(name) => println!("Found: {}", name),
None => println!("User not found"),
}
// unwrap_or for defaults
let name = find_user(99).unwrap_or(String::from("Unknown"));
println!("Name: {}", name);
// map for transformations
let upper = find_user(1)
.map(|name| name.to_uppercase())
.unwrap_or_default();
println!("Upper: {}", upper);
}
// Result for error handling
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("Division by zero"))
} else {
Ok(a / b)
}
}
fn main() {
match divide(10.0, 3.0) {
Ok(result) => println!("Result: {:.2}", result),
Err(e) => println!("Error: {}", e),
}
}
match exhaustively — the compiler ensures you handle every possible variant. The _ pattern catches everything else. This eliminates unhandled cases at compile time.
impl blocks for methods and associated functionsOption<T> replaces null — the compiler forces you to handle the absent caseResult<T, E> replaces exceptions — errors are values, not control flowmatch is exhaustive and compiler-checkedOption<T> replace in Rust?// Generic function — works with any type
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in &list[1..] {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let numbers = vec![34, 50, 25, 100, 65];
println!("Largest: {}", largest(&numbers));
let chars = vec!['y', 'm', 'a', 'q'];
println!("Largest: {}", largest(&chars));
}
trait Summary {
// Required method — must be implemented
fn summarize(&self) -> String;
// Default implementation — optional override
fn preview(&self) -> String {
format!("{}...", &self.summarize()[..20])
}
}
struct Article {
title: String,
author: String,
content: String,
}
impl Summary for Article {
fn summarize(&self) -> String {
format!("{}, by {} — {}", self.title, self.author, &self.content[..50])
}
}
struct Tweet {
username: String,
content: String,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("@{}: {}", self.username, self.content)
}
}
fn main() {
let article = Article {
title: String::from("Rust is Great"),
author: String::from("Mayank"),
content: String::from("Rust provides memory safety without a garbage collector..."),
};
println!("{}", article.summarize());
}
use std::fmt::{Display, Debug};
// Trait bound syntax
fn print_info<T: Display + Debug>(item: &T) {
println!("Display: {}", item);
println!("Debug: {:?}", item);
}
// impl Trait syntax — cleaner for simple cases
fn print_item(item: &impl Display) {
println!("{}", item);
}
// Where clause — complex bounds
fn process<T, U>(t: &T, u: &U) -> String
where
T: Display + Clone,
U: Debug + PartialOrd,
{
format!("{}: {:?}", t, u)
}
// Trait as return type
fn make_greeting(name: &str) -> impl Display {
format!("Hello, {}!", name)
}
fn main() {
print_info(&42);
print_item(&"hello");
println!("{}", process(&"test", &3.14));
}
trait Iterator {
type Item; // associated type — one implementation per type
fn next(&mut self) -> Option<Self::Item>;
}
struct Counter {
count: u32,
max: u32,
}
impl Counter {
fn new(max: u32) -> Counter {
Counter { count: 0, max }
}
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<u32> {
if self.count < self.max {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
fn main() {
let counter = Counter::new(5);
let values: Vec<u32> = counter.collect();
println!("{:?}", values); // [1, 2, 3, 4, 5]
}
#[derive(Debug, Clone, PartialEq)]
struct Point {
x: f64,
y: f64,
}
#[derive(Debug)]
enum Color {
Red,
Green,
Blue,
}
fn main() {
let p1 = Point { x: 1.0, y: 2.0 };
let p2 = p1.clone();
println!("{:?}", p1); // Point { x: 1.0, y: 2.0 }
println!("Equal: {}", p1 == p2); // true
let color = Color::Red;
println!("{:?}", color); // Red
}
<T: Trait>) to constrain genericsItem per Iterator)#[derive] auto-implements common traits like Debug, Clone, PartialEquse std::fs;
use std::num::ParseIntError;
fn read_number(path: &str) -> Result<i32, Box<dyn std::error::Error>> {
let content = fs::read_to_string(path)?; // ? propagates errors
let number = content.trim().parse::<i32>()?; // ? propagates parse errors
Ok(number)
}
fn main() {
match read_number("number.txt") {
Ok(n) => println!("Number: {}", n),
Err(e) => println!("Error: {}", e),
}
}
use std::fs::File;
use std::io::{self, Read};
// Without ? — verbose
fn read_file_verbose(path: &str) -> Result<String, io::Error> {
let file = match File::open(path) {
Ok(f) => f,
Err(e) => return Err(e),
};
let mut contents = String::new();
match file.read_to_string(&mut contents) {
Ok(_) => Ok(contents),
Err(e) => Err(e),
}
}
// With ? — clean and idiomatic
fn read_file(path: &str) -> Result<String, io::Error> {
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
// Chained
fn read_file_chained(path: &str) -> Result<String, io::Error> {
let mut contents = String::new();
File::open(path)?.read_to_string(&mut contents)?;
Ok(contents)
}
use std::fmt;
#[derive(Debug)]
enum AppError {
NotFound(String),
ParseError(String),
PermissionDenied,
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AppError::NotFound(s) => write!(f, "Not found: {}", s),
AppError::ParseError(s) => write!(f, "Parse error: {}", s),
AppError::PermissionDenied => write!(f, "Permission denied"),
}
}
}
impl std::error::Error for AppError {}
fn find_config(name: &str) -> Result<String, AppError> {
match name {
"prod" => Ok(String::from("production config")),
"dev" => Ok(String::from("development config")),
_ => Err(AppError::NotFound(name.to_string())),
}
}
fn main() {
match find_config("staging") {
Ok(config) => println!("{}", config),
Err(AppError::NotFound(name)) => println!("Config '{}' not found", name),
Err(e) => println!("Error: {}", e),
}
}
// Cargo.toml:
// [dependencies]
// anyhow = "1"
// thiserror = "1"
use anyhow::{Context, Result};
use thiserror::Error;
// thiserror — derive macro for custom errors
#[derive(Error, Debug)]
enum DatabaseError {
#[error("Connection failed: {0}")]
Connection(String),
#[error("Query failed: {0}")]
Query(String),
#[error(transparent)]
Other(#[from] std::io::Error),
}
// anyhow — ergonomic error handling for applications
fn read_config() -> Result<String> {
let content = std::fs::read_to_string("config.toml")
.context("Failed to read config file")?;
Ok(content)
}
fn main() -> Result<()> {
let config = read_config()?;
println!("{}", config);
Ok(())
}
anyhow for applications (quick error handling with context) and thiserror for libraries (structured, typed errors that callers can match on).
Result<T, E>? operator propagates errors automatically — no try/catchDisplay and Error trait implementationsthiserror for library errors, anyhow for application errors.context() adds human-readable error messages to anyhow errorsuse std::thread;
use std::time::Duration;
fn main() {
// Spawn a thread
let handle = thread::spawn(|| {
for i in 1..=5 {
println!("Spawned thread: {}", i);
thread::sleep(Duration::from_millis(100));
}
});
for i in 1..=3 {
println!("Main thread: {}", i);
thread::sleep(Duration::from_millis(150));
}
handle.join().unwrap(); // wait for thread to finish
// Move data into thread
let names = vec!["Mayank", "Alice", "Bob"];
let handle = thread::spawn(move || {
for name in names {
println!("Hello, {}!", name);
}
});
handle.join().unwrap();
}
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// Arc = Atomic Reference Counting (shared ownership)
// Mutex = Mutual Exclusion (interior mutability)
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();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Final count: {}", *counter.lock().unwrap()); // 10
}
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
// mpsc = Multiple Producer, Single Consumer
let (tx, rx) = mpsc::channel();
// Producer 1
let tx1 = tx.clone();
thread::spawn(move || {
let messages = vec!["hello", "from", "thread 1"];
for msg in messages {
tx1.send(msg.to_string()).unwrap();
thread::sleep(Duration::from_millis(200));
}
});
// Producer 2
thread::spawn(move || {
let messages = vec!["hi", "from", "thread 2"];
for msg in messages {
tx.send(msg.to_string()).unwrap();
thread::sleep(Duration::from_millis(300));
}
});
// Receive all messages
for received in rx {
println!("Got: {}", received);
}
}
use std::rc::Rc;
use std::sync::Arc;
fn main() {
// Send — can be transferred between threads
// Sync — can be referenced from multiple threads
// Arc is Send + Sync — safe for concurrent access
let data = Arc::new(vec![1, 2, 3]);
// Rc is NOT Send — can't move to another thread
// let bad = Rc::new(5);
// thread::spawn(move || println!("{}", bad)); // COMPILE ERROR!
// Clone Arc for each thread
let data1 = Arc::clone(&data);
let data2 = Arc::clone(&data);
let h1 = std::thread::spawn(move || println!("{:?}", data1));
let h2 = std::thread::spawn(move || println!("{:?}", data2));
h1.join().unwrap();
h2.join().unwrap();
}
// Cargo.toml: rayon = "1"
use rayon::prelude::*;
fn main() {
let numbers: Vec<u64> = (1..=10_000_000).collect();
// Sequential
let sum1: u64 = numbers.iter().sum();
// Parallel — just change iter() to par_iter()
let sum2: u64 = numbers.par_iter().sum();
// Parallel map/filter
let squares: Vec<u64> = (1..=1_000_000)
.into_par_iter()
.filter(|x| x % 2 == 0)
.map(|x| x * x)
.collect();
println!("Sequential sum: {}", sum1);
println!("Parallel sum: {}", sum2);
println!("Even squares count: {}", squares.len());
}
.iter() to .par_iter() — zero code restructuring needed.
Arc<Mutex<T>> is the standard pattern for shared mutable statempsc) enable message-passing concurrencyrayon makes parallelism trivial with parallel iteratorsRc<T> to another thread?fn main() {
let mut num = 42;
// Creating raw pointers — allowed in safe code
let r1 = &num as *const i32; // immutable raw pointer
let r2 = &mut num as *mut i32; // mutable raw pointer
// Dereferencing requires unsafe
unsafe {
println!("r1: {}", *r1);
println!("r2: {}", *r2);
*r2 = 100;
println!("num: {}", num);
}
// Creating a pointer from an address
let address = 0x012345usize;
let _r = address as *const i32;
// Dereferencing an arbitrary address is undefined behavior!
}
// Unsafe lets you do 5 things that safe Rust forbids:
// 1. Dereference a raw pointer
// 2. Call an unsafe function or method
// 3. Access or modify a mutable static variable
// 4. Implement an unsafe trait
// 5. Access union fields
// Example: splitting a slice
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = slice.len();
assert!(mid <= len);
let ptr = slice.as_mut_ptr();
unsafe {
(
std::slice::from_raw_parts_mut(ptr, mid),
std::slice::from_raw_parts_mut(ptr.add(mid), len - mid),
)
}
}
fn main() {
let mut v = vec![1, 2, 3, 4, 5, 6];
let (left, right) = split_at_mut(&mut v, 3);
println!("Left: {:?}, Right: {:?}", left, right);
}
// Calling C functions from Rust
extern "C" {
fn abs(input: i32) -> i32;
fn sqrt(input: f64) -> f64;
}
// Exposing Rust functions to C
#[no_mangle]
pub extern "C" fn rust_function(x: i32) -> i32 {
x * 2
}
fn main() {
unsafe {
println!("C abs(-5) = {}", abs(-5));
println!("C sqrt(9.0) = {}", sqrt(9.0));
}
}
// Using libc crate for more C bindings
// Cargo.toml: libc = "0.2"
use std::ffi::CString;
use std::os::raw::c_char;
extern "C" {
fn getenv(name: *const c_char) -> *mut c_char;
}
fn safe_getenv(name: &str) -> Option<String> {
let c_name = CString::new(name).ok()?;
unsafe {
let ptr = getenv(c_name.as_ptr());
if ptr.is_null() {
None
} else {
let c_str = std::ffi::CStr::from_ptr(ptr);
Some(c_str.to_string_lossy().into_owned())
}
}
}
// Safe abstraction over unsafe code — the Rust way
struct SafeBuffer {
ptr: *mut u8,
len: usize,
capacity: usize,
}
impl SafeBuffer {
fn new(capacity: usize) -> SafeBuffer {
let layout = std::alloc::Layout::array::<u8>(capacity).unwrap();
let ptr = unsafe { std::alloc::alloc(layout) };
if ptr.is_null() {
std::alloc::handle_alloc_error(layout);
}
SafeBuffer { ptr, len: 0, capacity }
}
// Safe public API — callers never touch unsafe
fn push(&mut self, byte: u8) {
if self.len == self.capacity {
panic!("Buffer full");
}
unsafe {
self.ptr.add(self.len).write(byte);
}
self.len += 1;
}
fn as_slice(&self) -> &[u8] {
unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
}
}
impl Drop for SafeBuffer {
fn drop(&mut self) {
let layout = std::alloc::Layout::array::<u8>(self.capacity).unwrap();
unsafe {
std::alloc::dealloc(self.ptr, layout);
}
}
}
fn main() {
let mut buf = SafeBuffer::new(10);
buf.push(b'H');
buf.push(b'i');
println!("{:?}", buf.as_slice()); // [72, 105]
}
// SAFETY: ....
unsafe unlock?