Rust is a systems programming language that prioritizes safety, speed, and concurrency without relying on a garbage collector. Its unique ownership model enforces memory safety and makes system-level programming more efficient. Rust is widely used in operating systems, game engines, and web applications for its performance and reliability, Also in some Web Clients using WASM.

Few things I like about Rust are

  • The ownership system, which ensures memory safety without a garbage collector.
  • The powerful match statement for pattern matching and error handling.
  • The zero-cost abstractions provided by traits and generics.

Few things I dislike about Rust are

  • The steep learning curve due to its unique ownership system and strict compiler checks.
  • Rust programs are hard to change, It’s not a good language to write prototypes or throwaway code.

Variables and Mutability

Variables in Rust are immutable by default, if we want to change the value of a variable, we need to specify it as mutable using the mut keyword. Rust also supports constants, which are always immutable.

Syntax and Example:

// Variable declaration
let x = 5; // Immutable variable
let mut y = 5; // Mutable variable

// Constant declaration
const MAX_POINTS: u32 = 100_000; // Constant declaration

// Type inference
let x = 5; // Inferred as i32
let y: u32 = "42".parse().expect("Not a number!"); // Explicit type annotation with parse

Control Flow

Rust offers familiar control structures with some additional features, like pattern matching with match and safe unwrapping of Option<T> and Result<T, E> types.

Syntax and Example:

let condition = true;

// if else statement
if condition {
    println!("condition is true");
} else {
    println!("condition is false");
}

// Using `if` in an expression
let number = if condition { 5 } else { 6 }; // Using `if` in a let statement

// Looping with `while`
while number != 0 {
    println!("{}!", number);
    number -= 1;
}

// infinite loop with `loop`
loop {
    println!("again!");
    break;
}

// pattern matching
match number {
    1 => println!("One"),
    2 => println!("Two"),
    _ => println!("Other"),
}

// if let
let some_u8_value = Some(0u8);
if let Some(3) = some_u8_value {
    println!("three");
}

Functions

Functions in Rust are declared with fn and can return values directly or through a Result type for error handling.

There are implicit and explicit returns for functions, the statement without ; at the end is considered as a return value in rust.

Syntax and Example:

// Function declaration
fn add(x: i32, y: i32) -> i32 {
    x + y // Implicit return
    // return x + y; // Explicit return
}
// Function call
println!("The sum is: {}", add(10, 20));

// anonymous function
let add = |x: i32, y: i32| -> i32 { x + y };

// function with multiple return values
fn get_person() -> (String, i32) {
    (String::from("Alice"), 30)
}
let (name, age) = get_person();

// Function returning a Result
fn divide(numerator: f64, denominator: f64) -> Result<f64, &'static str> {
    if denominator == 0.0 {
        Err("Denominator cannot be zero")
    } else {
        Ok(numerator / denominator)
    }
}

Data Structures (Structs and Enums)

Rust uses structs for custom data types and enums for types with a fixed set of variants, including the powerful Option<T> and Result<T, E> for error handling.

Syntax and Example:

struct Point {
    x: i32,
    y: i32,
}

enum WebEvent {
    PageLoad,
    PageUnload,
}

let point = Point { x: 10, y: 20 }; // Creating an instance of a struct
// Accessing fields of a struct
println!("Point coordinates: ({}, {})", point.x, point.y);

let event = WebEvent::PageLoad; // Using an enum variant

Traits

Traits define shared behavior in a way similar to interfaces in other languages but are implemented with a focus on compile-time safety and zero runtime overhead.

Syntax and Example:

trait DataStore {
    fn save(&self, data: &str) -> Result<(), &str>;
    fn load(&self) -> Result<String, &str>;
}

struct SQlite {
    db: sql::DB,
}

impl DataStore for SQlite {
    fn save(&self, data: &str) -> Result<(), &str> {
        // Save data to SQLite
    }
    fn load(&self) -> Result<String, &str> {
        // Load data from SQLite
    }
}

Collection Manipulation

Rust provides several powerful collections, such as vectors, hash maps, and others, for storing and manipulating groups of values.

Syntax and Example:

// Array - stack-allocated list
let arr = [1, 2, 3]; // [i32; 3]
let first = arr[0];

// Vector - heap-allocated list
let mut vec = vec![1, 2, 3]; // Vec<i32>
vec.push(4);
// Iterating through a vector
for number in &vec {
    println!("{}", number);
}
// vector foreach
vec.iter().for_each(|x| println!("{}", x));
// vector map
let doubled: Vec<i32> = vec.iter().map(|x| x * 2).collect();
// vector filter
let even: Vec<i32> = vec.into_iter().filter(|x| x % 2 == 0).collect();
// vector reduce ( fold )
let sum: i32 = vec.iter().fold(0, |acc, x| acc + x);

let mut scores = std::collections::HashMap::new();
scores.insert("Blue", 10);
scores.insert("Yellow", 50);
// Iterating through a hash map
for (key, value) in &scores {
    println!("{}: {}", key, value);
}
// Accessing a value from a hash map
let score = scores.get("Blue");
// Removing from a hash map
scores.remove("Yellow");
// hash map foreach
scores.iter().for_each(|(key, value)| println!("{}: {}", key, value));
// hash map map
let doubled: HashMap<&str, i32> = scores.iter().map(|(key, value)| (key, value * 2)).collect();
// hash map filter
let high_scores: HashMap<&str, i32> = scores.into_iter().filter(|(_, value)| *value > 25).collect();
// hash map reduce ( fold )
let sum: i32 = scores.iter().fold(0, |acc, (_, value)| acc + value);

Error Handling

Rust encourages using Result<T, E> for recoverable errors and Option<T> for optional values, with various patterns for handling these cases.

Syntax and Example:

// Function returning Error

// Function returning a Result
fn divide(numerator: f64, denominator: f64) -> Result<f64, &'static str> {
    if denominator == 0.0 {
        Err("Denominator cannot be zero")
    } else {
        Ok(numerator / denominator)
    }
}

// Error checking

// `unwrap` and `expect` - Quick unwrapping methods, may panic
let file_path = "example.txt";
let content = File::open(file_path).unwrap(); // This will panic if file not found
let content = File::open(file_path).expect("Failed to open file"); // This will panic with a custom message if file not found


// Matching on `Result` and `Option` - Explicit handling of all cases
match divide(10.0, 0.0) {
    Ok(result) => println!("Division result: {}", result),
    Err(e) => println!("Error: {}", e),
}

// `if let` - Concise pattern matching for `Option` and `Result`
if let Some(value) = some_option {
    println!("Value: {}", value);
}

// error propagation using `?`
fn read_username_from_file() -> Result<String, io::Error> {
    let mut f = File::open("username.txt")?; // This will return Err if file not found
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

Concurrency

Rust’s approach to concurrency is designed to be safe and prevent data races, leveraging ownership and type checks at compile time.

Syntax and Example:

use std::thread;
use std::time::Duration;

thread::spawn(move || {
    for i in 1..10 {
        println!("hi number {} from the spawned thread!", i);
        thread::sleep(Duration::from_millis(1));
    }
}).join().unwrap(); // `join` waits for the thread to finish

// with tokio
#[tokio::main]
async fn main() {
    let handle = tokio::spawn(async {
        // asynchronous code
    });
    handle.await.unwrap();
}

Ecosystem

Installation

Rust can be installed using the official installer or through package managers like rustup for managing multiple Rust toolchains.

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

or using Homebrew

brew install rustup
rustup-init

Hello World

fn main() {
    println!("Hello, World!");
}

Build and Run

# Run the program
cargo run

# Build the program
cargo build

CLI

  • cargo new <project-name>: Create a new Rust project
  • cargo run: Compile and run the Rust program
  • cargo build: Build the Rust project
    • cargo build --target x86_64-unknown-linux-gnu for Linux
    • cargo build --target x86_64-pc-windows-msvc for Windows
    • cargo build --target x86_64-apple-darwin for macOS
  • cargo test: Run tests
  • cargo fmt: Format the code
  • cargo check: Quickly check your code for errors without producing an executable

Package Management

  • cargo add <package-name>: Add a package to your dependencies (requires cargo-edit)
  • cargo update: Update dependencies in Cargo.lock
  • cargo install <package-name>: Install a Rust package globally
  • Axum - Web framework
  • Tokio - Asynchronous runtime
  • Serde - Framework for serializing and deserializing Rust data structures efficiently and generically
  • Diesel - Safe, extensible ORM and Query Builder for Rust
  • Clap - Command Line Argument Parser for Rust
  • Rocket - Web framework for Rust with a focus on ease-of-use, expressibility, and speed

Special Features

  • Ownership System: Rust’s most distinctive feature, the ownership system, ensures memory safety without needing a garbage collector, enabling safe concurrency and efficient resource management.
  • Zero-Cost Abstractions: Traits and generics in Rust allow for powerful abstractions without runtime overhead, leading to efficient and reusable code.
  • Match and Pattern Matching: Rust’s match statements and pattern matching offer a declarative way to handle various cases, including error handling, without boilerplate code.
  • Cargo and Crates: Rust’s package ecosystem, centered around Cargo and crates.io, provides a vast library of reusable code, facilitating rapid development while ensuring dependency stability and security.