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 projectcargo run
: Compile and run the Rust programcargo build
: Build the Rust projectcargo build --target x86_64-unknown-linux-gnu
for Linuxcargo build --target x86_64-pc-windows-msvc
for Windowscargo build --target x86_64-apple-darwin
for macOS
cargo test
: Run testscargo fmt
: Format the codecargo check
: Quickly check your code for errors without producing an executable
Package Management
cargo add <package-name>
: Add a package to your dependencies (requirescargo-edit
)cargo update
: Update dependencies in Cargo.lockcargo install <package-name>
: Install a Rust package globally
Popular Libraries
- 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.