Rust Fundamentals Cheat Sheet

A quick reference guide for Rust programming language fundamentals

1. Installation & Setup

What Is Installation & Setup?

Setting up Rust involves installing the Rust compiler, package manager, and tools to start developing Rust applications. This ensures you have the necessary environment to write, build, and run Rust programs.

Installing Rust

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

This command installs Rust using the official Rustup installer, which manages Rust versions and tools.

Verifying Installation

rustc --version
cargo --version

These commands verify that the Rust compiler (rustc) and package manager (cargo) are installed correctly.

Ways of Implementation

2. Language Basics

What Are Language Basics?

Language basics in Rust introduce fundamental concepts like defining the entry point, working with variables, and adding comments. These are essential for writing any Rust program.

Main Function

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

The main function is the entry point of every Rust program.

Variables & Mutability

// Immutable by default
let x = 5;
// x = 6; // Error!

// Mutable variable
let mut y = 5;
y = 6; // Works!

// Constants
const MAX_POINTS: u32 = 100_000;

// Shadowing
let z = 5;
let z = z + 1; // Creates a new variable, shadows previous z

Rust variables are immutable by default. Use mut for mutability and const for constants.

Comments

// Line comment

/* Block comment
   Can span multiple lines */

/// Documentation comment (generates docs)
/// # Examples
/// ```
/// let five = 5;
/// ```

Comments in Rust include line comments, block comments, and documentation comments for generating API docs.

3. Data Types

Scalar Types

Type Examples Description
Integer i8, i16, i32, i64, i128, isize
u8, u16, u32, u64, u128, usize
Signed and unsigned integers of various sizes
Floating-point f32, f64 IEEE-754 floating point numbers
Boolean bool true or false
Character char Unicode scalar value (4 bytes)

Integer Examples

let a = 98_222; // Decimal
let b = 0xff;    // Hex
let c = 0o77;    // Octal
let d = 0b1111_0000; // Binary
let e = b'A';    // Byte (u8 only)

Compound Types

Tuples

let tup: (i32, f64, char) = (500, 6.4, 'A');

// Destructuring
let (x, y, z) = tup;
println!("y is: {}", y);

// Accessing by index
let five_hundred = tup.0;
let six_point_four = tup.1;

Arrays

// Fixed-length array
let arr: [i32; 5] = [1, 2, 3, 4, 5];

// Accessing elements
let first = arr[0];

// Array with same value repeated
let zeros = [0; 10]; // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

4. String Handling

String Types

Type Description
&str String slice, immutable reference to UTF-8 encoded string data
String Owned string type, growable, mutable, heap-allocated

String Literals (&str)

let s = "Hello, world!"; // s has type &str

String Type

// Creating a new String
let s = String::from("Hello");
let s = "Hello".to_string();

// Appending
let mut s = String::from("Hello");
s.push_str(", world!"); // s is now "Hello, world!"
s.push('!'); // Add a single character

Converting Between Types

// &str to String
let s1: &str = "hello";
let s2: String = s1.to_string();
let s3: String = s1.to_owned();
let s4: String = String::from(s1);

// String to &str
let s1: String = String::from("hello");
let s2: &str = &s1;
let s3: &str = s1.as_str();

String Operations

// Concatenation
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // Note: s1 is moved here

// Format macro
let s1 = String::from("Hello");
let s2 = String::from("world");
let s3 = format!("{}, {}!", s1, s2); // s1 and s2 are still valid

// Slicing
let hello = "Hello, world!";
let s = &hello[0..5]; // s = "Hello"

5. Ownership & Borrowing

What Is Ownership & Borrowing?

Ownership and borrowing are core concepts in Rust that ensure memory safety without a garbage collector. Ownership defines how memory is managed, while borrowing allows temporary access to data without transferring ownership.

Ownership Rules

  1. Each value in Rust has an owner
  2. There can only be one owner at a time
  3. When the owner goes out of scope, the value is dropped

Move Semantics

let s1 = String::from("hello");
let s2 = s1; // s1 is moved to s2 and is no longer valid
// println!("{}", s1); // Error!

// Clone for deep copy
let s1 = String::from("hello");
let s2 = s1.clone(); // s1 is still valid

Primitive types implement the Copy trait and are copied rather than moved:

let x = 5; let y = x; // Both x and y are valid

Functions and Ownership

fn main() {
    let s = String::from("hello");
    takes_ownership(s); // s is moved and is no longer valid here
    
    let x = 5;
    makes_copy(x); // x is copied, still valid here
}

fn takes_ownership(s: String) {
    println!("{}", s);
} // s goes out of scope and is dropped

fn makes_copy(x: i32) {
    println!("{}", x);
} // x goes out of scope, nothing special happens

Borrowing with References

fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1); // passing a reference
    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
} // s goes out of scope, but it doesn't own the value, so nothing happens

Mutable References

fn main() {
    let mut s = String::from("hello");
    change(&mut s);
    println!("{}", s); // prints "hello, world"
}

fn change(s: &mut String) {
    s.push_str(", world");
}

Reference Rules

  1. You can have either:
    • One mutable reference
    • Any number of immutable references
  2. References must always be valid (no dangling references)
let mut s = String::from("hello");

// This works
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
// r1 and r2 are no longer used after this point

// This also works
let r3 = &mut s;
println!("{}", r3);

// This would ERROR - can't have immutable and mutable refs at same time
// let r1 = &s;
// let r3 = &mut s;
// println!("{} and {}", r1, r3);

6. Lifetimes

What Are Lifetimes?

Lifetimes in Rust ensure that references are valid for as long as they are used, preventing dangling references and ensuring memory safety.

Lifetime Annotations

// Lifetime parameter 'a
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

Lifetime annotations explicitly specify the scope of references to ensure they are valid.

Lifetimes in Structs

// Struct with reference that needs a lifetime
struct ImportantExcerpt<'a> {
    part: &'a str,
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().unwrap();
    let i = ImportantExcerpt {
        part: first_sentence,
    };
}

Structs with references require lifetime annotations to ensure the references remain valid.

Ways of Implementation

7. Control Flow

What Is Control Flow?

Control flow structures in Rust allow decision-making and iteration, enabling programs to execute different code paths based on conditions.

if Expressions

// Basic if
let number = 6;
if number % 4 == 0 {
    println!("number is divisible by 4");
} else if number % 3 == 0 {
    println!("number is divisible by 3");
} else if number % 2 == 0 {
    println!("number is divisible by 2");
} else {
    println!("number is not divisible by 4, 3, or 2");
}

// if in a let statement
let condition = true;
let number = if condition { 5 } else { 6 };

Ways of Implementation

8. Functions & Closures

What Are Functions & Closures?

Functions and closures are reusable blocks of code in Rust. Functions are named and can take parameters, while closures are anonymous functions that can capture variables from their environment.

Function Definition

// Basic function
fn greet(name: &str) {
    println!("Hello, {}!", name);
}

// Function with return value
fn add(a: i32, b: i32) -> i32 {
    a + b // No semicolon means this is an expression that returns a value
}

// Early return with explicit return statement
fn abs(x: i32) -> i32 {
    if x >= 0 {
        return x;
    }
    -x
}

Closures

// Basic closure syntax
let add_one = |x| x + 1;
let sum = add_one(5); // 6

// Closure with type annotations
let add_two = |x: i32| -> i32 { x + 2 };

// Closure that captures environment
let x = 4;
let equal_to_x = |z| z == x;
let result = equal_to_x(4); // true

Ways of Implementation

9. Structs

Understanding Structs in Rust

Structs (structures) are custom data types that let you package and name multiple related values that make up a meaningful group. They are similar to objects in other languages but designed with Rust's ownership rules in mind.

Types of Structs

Rust provides three types of structures:

// 1. Classic Struct with named fields
struct Employee {
    name: String,
    position: String,
    salary: u32,
    active: bool,
}

// 2. Tuple Struct (unnamed fields)
struct Color(u8, u8, u8); // RGB color

// 3. Unit Struct (no fields)
struct UnitStruct; // Used for trait implementations

Creating and Using Structs

Structs can be instantiated and modified in several ways:

// Creating instances
let mut employee = Employee {
    name: String::from("John Doe"),
    position: String::from("Developer"),
    salary: 50000,
    active: true,
};

// Field shorthand when variables have same names as fields
let name = String::from("Jane Doe");
let position = String::from("Manager");
let employee2 = Employee {
    name,    // same as name: name
    position, // same as position: position
    salary: 60000,
    active: true,
};

// Updating based on another instance
let employee3 = Employee {
    salary: 55000,
    ..employee2 // copy other fields from employee2
};

Methods and Associated Functions

Structs can have methods (functions associated with the type):

struct Circle {
    radius: f64,
    center: Point,
}

impl Circle {
    // Associated function (constructor)
    fn new(x: f64, y: f64, radius: f64) -> Circle {
        Circle {
            center: Point { x, y },
            radius,
        }
    }

    // Method that calculates area
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }

    // Method that modifies the circle
    fn scale(&mut self, factor: f64) {
        self.radius *= factor;
    }

    // Method that checks if a point is inside the circle
    fn contains(&self, point: &Point) -> bool {
        let dx = self.center.x - point.x;
        let dy = self.center.y - point.y;
        dx * dx + dy * dy <= self.radius * self.radius
    }
}

// Usage example
fn main() {
    let mut circle = Circle::new(0.0, 0.0, 5.0);
    println!("Area: {}", circle.area());
    
    circle.scale(2.0);
    println!("New area: {}", circle.area());
    
    let point = Point { x: 3.0, y: 4.0 };
    println!("Contains point: {}", circle.contains(&point));
}

Advanced Features

Structs can implement various traits for added functionality:

#[derive(Debug, Clone, PartialEq)]
struct Product {
    id: u32,
    name: String,
    price: f64,
}

impl Default for Product {
    fn default() -> Self {
        Product {
            id: 0,
            name: String::from("Unnamed"),
            price: 0.0,
        }
    }
}

impl std::fmt::Display for Product {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{}: {} (${:.2})", self.id, self.name, self.price)
    }
}

// Usage with derived and implemented traits
fn main() {
    let product1 = Product {
        id: 1,
        name: String::from("Widget"),
        price: 19.99,
    };
    
    // Using Debug trait
    println!("Debug: {:?}", product1);
    
    // Using Display trait
    println!("Display: {}", product1);
    
    // Using Default trait
    let product2 = Product::default();
    
    // Using PartialEq trait
    if product1 != product2 {
        println!("Products are different!");
    }
}

Best Practices

10. Enums & Pattern Matching

What Are Enums & Pattern Matching?

Enums (enumerations) are a powerful feature in Rust that allow you to define a type by listing all of its possible variants. They are particularly useful when you have a value that could be one of several predefined possibilities. Pattern matching provides a mechanism to handle these variants in a clear and exhaustive way.

Basic Enums

At their simplest, enums are a way to represent a fixed set of options:

// Define an enum for compass directions
enum Direction {
    North,
    South,
    East,
    West,
}

// Using the enum
let player_direction = Direction::North;

// Using with match
match player_direction {
    Direction::North => println!("Moving up"),
    Direction::South => println!("Moving down"),
    Direction::East => println!("Moving right"),
    Direction::West => println!("Moving left"),
}

Enums with Data

Enums can hold data within their variants, making them more powerful:

// Enum variants can hold different types of data
enum GameEvent {
    Score(u32),                     // Tuple variant
    Move { x: i32, y: i32 },       // Struct variant
    TeamName(String),               // Single value variant
    Quit,                          // Unit variant
}

// Using enum with data
let events = vec![
    GameEvent::Score(10),
    GameEvent::Move { x: 2, y: -1 },
    GameEvent::TeamName(String::from("Blue Team")),
    GameEvent::Quit,
];

// Pattern matching with data extraction
for event in events {
    match event {
        GameEvent::Score(points) => println!("Scored {} points!", points),
        GameEvent::Move { x, y } => println!("Moved to ({}, {})", x, y),
        GameEvent::TeamName(name) => println!("Team name: {}", name),
        GameEvent::Quit => println!("Game Over!"),
    }
}

Option and Result Enums

Rust's standard library includes two very important enums for handling optional values and errors:

// Option enum for optional values
fn divide(numerator: f64, denominator: f64) -> Option {
    if denominator == 0.0 {
        None
    } else {
        Some(numerator / denominator)
    }
}

// Result enum for error handling
#[derive(Debug)]
enum MathError {
    DivisionByZero,
    NegativeSquareRoot,
}

fn safe_operations(x: f64, y: f64) -> Result {
    if y == 0.0 {
        return Err(MathError::DivisionByZero);
    }
    
    let result = x / y;
    if result < 0.0 {
        return Err(MathError::NegativeSquareRoot);
    }
    
    Ok(result.sqrt())
}

Advanced Pattern Matching

Pattern matching in Rust is powerful and supports multiple patterns and guards:

enum Temperature {
    Celsius(f64),
    Fahrenheit(f64),
}

let temp = Temperature::Celsius(23.0);

match temp {
    Temperature::Celsius(c) if c > 30.0 => println!("Too hot!"),
    Temperature::Celsius(c) if c < 10.0 => println!("Too cold!"),
    Temperature::Celsius(c) => println!("Nice temperature: {}°C", c),
    Temperature::Fahrenheit(f) if f > 86.0 => println!("Too hot!"),
    Temperature::Fahrenheit(f) if f < 50.0 => println!("Too cold!"),
    Temperature::Fahrenheit(f) => println!("Nice temperature: {}°F", f),
}

// Multiple patterns using |
let number = 7;
match number {
    1 | 2 | 3 => println!("Low number"),
    4 | 5 | 6 => println!("Medium number"),
    _ => println!("High number"),
}

Best Practices

11. Error Handling

What Is Error Handling?

Error handling in Rust ensures robust and predictable programs. It is a core feature of the language, designed to help developers write safe and reliable code by addressing potential issues at compile time or runtime.

Result Enum

// Result is defined in the standard library:
// enum Result {
//     Ok(T),
//     Err(E),
// }

fn divide(numerator: f64, denominator: f64) -> Result {
    if denominator == 0.0 {
        Err(String::from("Cannot divide by zero"))
    } else {
        Ok(numerator / denominator)
    }
}

Handling Results with match

let result = divide(10.0, 2.0);

match result {
    Ok(value) => println!("The result is {}", value),
    Err(error) => println!("Error: {}", error),
}

Propagating Errors

fn read_username_from_file() -> Result {
    let f = File::open("username.txt");

    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut s = String::new();

    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}

The ? Operator

// Same function using ? operator
fn read_username_from_file() -> Result {
    let mut f = File::open("username.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

// Even shorter using method chaining
fn read_username_from_file() -> Result {
    let mut s = String::new();
    File::open("username.txt")?.read_to_string(&mut s)?;
    Ok(s)
}

panic! Macro

// Causes program to crash with message
panic!("crash and burn");

// Unwrap will panic if Result is Err
let f = File::open("test.txt").unwrap();

// Expect provides a custom panic message
let f = File::open("test.txt")
    .expect("Failed to open test.txt");

12. Common Collections

What Are Common Collections?

Common collections in Rust are data structures that allow efficient storage and manipulation of groups of data. They include:

Vec<T>

// Creating a vector
let v: Vec = Vec::new();
let v = vec![1, 2, 3];

// Adding elements
let mut v = Vec::new();
v.push(5);
v.push(6);

// Reading elements
let third: &i32 = &v[2];
match v.get(2) {
    Some(third) => println!("Third element is {}", third),
    None => println!("No third element"),
}

// Iterating
for i in &v {
    println!("{}", i);
}

// Mutably iterating
for i in &mut v {
    *i += 50;
}

HashMap<K, V>

use std::collections::HashMap;

// Creating a new hash map
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);

// Accessing values
let team_name = String::from("Blue");
let score = scores.get(&team_name);

// Updating values
scores.insert(String::from("Blue"), 25); // Overwrites
scores.entry(String::from("Red")).or_insert(50); // Only inserts if key has no value

// Updating based on old value
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
    let count = map.entry(word).or_insert(0);
    *count += 1;
}

13. Testing

What Is Testing?

Testing in Rust ensures code correctness and prevents regressions. It is a built-in feature that supports unit and integration tests.

Unit Tests

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }

    #[test]
    fn larger_can_hold_smaller() {
        let larger = Rectangle { width: 8, height: 7 };
        let smaller = Rectangle { width: 5, height: 1 };

        assert!(larger.can_hold(&smaller));
    }

    #[test]
    #[should_panic(expected = "Guess value must be less than or equal to 100")]
    fn greater_than_100() {
        Guess::new(200);
    }
}

Integration Tests

// in tests/integration_test.rs
use my_crate;

#[test]
fn it_adds_two() {
    assert_eq!(4, my_crate::add_two(2));
}

Running Tests

# Run all tests
cargo test

# Run specific test
cargo test test_name

# Run tests in parallel
cargo test -- --test-threads=1

# Show output even for passing tests
cargo test -- --nocapture

14. Modules & Packages

Understanding Modules & Packages in Rust

Modules and packages are Rust's building blocks for creating organized, reusable code structures. They provide a way to split code into logical units and manage visibility between components.

Module System Basics

Modules can be defined in separate files or in the same file. Here's how they work:

1. File-based Modules
// File: src/math.rs
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

pub fn multiply(a: i32, b: i32) -> i32 {
    a * b
}

pub mod advanced {
    pub fn power(base: i32, exp: i32) -> i32 {
        if exp == 0 { return 1; }
        base * self::power(base, exp - 1)
    }
}

// File: src/main.rs
mod math; // Declare the module first

fn main() {
    // Using specific functions
    use math::{add, multiply};
    let sum = add(4, 5);
    let product = multiply(4, 5);

    // Or using the full path
    let power = math::advanced::power(2, 3);
}
2. Module Tree Organization
// File: src/lib.rs
pub mod models;    // tells Rust to look for src/models.rs or src/models/mod.rs
pub mod services;  // tells Rust to look for src/services.rs or src/services/mod.rs
pub mod utils;     // tells Rust to look for src/utils.rs or src/utils/mod.rs

// File: src/models/mod.rs
pub mod user;      // tells Rust to look for src/models/user.rs
pub mod product;   // tells Rust to look for src/models/product.rs

// File: src/models/user.rs
pub struct User {
    pub id: u32,
    pub username: String,
}

// File: src/main.rs
use crate::models::user::User;  // when in the same crate
// or
use my_crate::models::user::User;  // when using as external crate

Module Visibility and Access

// File: src/auth.rs
pub struct Credentials {
    pub username: String,
    password: String,  // private by default
}

impl Credentials {
    pub fn new(username: String, password: String) -> Self {
        Self { username, password }
    }

    pub(crate) fn verify(&self) -> bool {  // visible only within the current crate
        self.password.len() >= 8
    }
}

// File: src/main.rs
mod auth;
use auth::Credentials;

fn main() {
    let creds = Credentials::new(
        String::from("user"),
        String::from("password123")
    );
    
    // Can access public fields
    println!("Username: {}", creds.username);
    
    // Can't access private fields
    // println!("Password: {}", creds.password); // Error!
    
    // Can call public methods
    if creds.verify() {
        println!("Valid credentials!");
    }
}

Package Structure

A typical Rust project structure:

my_project/
├── Cargo.toml          # Package manifest
├── src/
│   ├── main.rs         # Binary crate root
│   ├── lib.rs          # Library crate root (optional)
│   ├── models/         # Module directory
│   │   ├── mod.rs      # Module entry point
│   │   ├── user.rs     # User model module
│   │   └── product.rs  # Product model module
│   └── utils/          # Another module directory
│       ├── mod.rs      # Module entry point
│       └── helpers.rs  # Helper functions module
├── tests/              # Integration tests
└── examples/           # Example code

Using External Crates

// In Cargo.toml
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }

// In src/main.rs or src/lib.rs
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
pub struct Config {
    name: String,
    version: String,
}

// In any other file that needs Config
use crate::Config;  // if in the same crate
// or
use my_crate::Config;  // if using as external crate

Best Practices

15. Asynchronous Programming

What Is Asynchronous Programming?

Asynchronous programming in Rust allows you to write non-blocking code that can handle multiple tasks concurrently. It is built on the async and await keywords, enabling efficient and scalable applications.

// Using Tokio runtime
use tokio::time::{sleep, Duration};

async fn fetch_data() -> String {
    println!("Fetching data...");
    sleep(Duration::from_secs(2)).await; // Simulate network delay
    "Data fetched!".to_string()
}

#[tokio::main]
async fn main() {
    println!("Start");
    let result = fetch_data().await;
    println!("{}", result);
    println!("End");
}

Ways of Implementation

Asynchronous programming can be implemented using:

16. Synchronous Programming

What Is Synchronous Programming?

Synchronous programming in Rust involves executing tasks sequentially, where each task must complete before the next one starts. Rust works synchronously by default, making it straightforward to write and reason about programs without introducing concurrency.

use std::io;

fn main() {
    println!("Enter your name:");
    let mut name = String::new();
    io::stdin().read_line(&mut name).expect("Failed to read input");
    println!("Hello, {}!", name.trim());
}

17. Concurrency

What Is Concurrency?

Concurrency in Rust allows multiple tasks to run simultaneously, improving performance and responsiveness. It is particularly useful for applications that need to handle multiple independent tasks, such as processing user requests, handling I/O operations, or performing background computations.

// Using Channels for Message Passing
use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let message = String::from("Hello from the thread!");
        tx.send(message).unwrap();
    });

    let received = rx.recv().unwrap();
    println!("Received: {}", received);
}

// Using Async/Await
use tokio::time::{sleep, Duration};

async fn fetch_data() -> String {
    println!("Fetching data...");
    sleep(Duration::from_secs(2)).await; // Simulate network delay
    "Data fetched!".to_string()
}

#[tokio::main]
async fn main() {
    println!("Start");
    let result = fetch_data().await;
    println!("{}", result);
    println!("End");
}

18. Threading

What Is Threading?

Threading in Rust allows you to run multiple threads of execution in parallel. Rust's ownership model ensures memory safety and prevents data races, making it easier to write safe multithreaded programs.

// Spawning Threads
use std::thread;

fn main() {
    let handle = thread::spawn(|| {
        for i in 1..5 {
            println!("Thread: {}", i);
        }
    });

    for i in 1..5 {
        println!("Main: {}", i);
    }

    handle.join().unwrap(); // Wait for the thread to finish
}

// Shared State with Mutex
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    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!("Result: {}", *counter.lock().unwrap());
}

19. Traits

What Are Traits?

Traits define shared behavior across types, similar to interfaces in other languages. They enable polymorphism and code reuse in Rust.

Defining and Implementing Traits

// Define a trait
trait Summary {
    fn summarize(&self) -> String;
    
    // Default implementation
    fn default_summary(&self) -> String {
        String::from("(Read more...)")
    }
}

// Implement trait for a type
struct NewsArticle {
    headline: String,
    content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}", self.headline)
    }
}

Trait Bounds

// Using trait bounds
fn notify(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

// Multiple trait bounds
fn complex_function(item: &T) {
    // ...
}

// Where clause syntax
fn some_function(t: &T, u: &U) -> i32
    where T: Display + Clone,
          U: Clone + Debug
{
    // ...
}

Trait Objects

// Using trait objects for dynamic dispatch
pub trait Draw {
    fn draw(&self);
}

pub struct Screen {
    pub components: Vec>,
}

impl Screen {
    pub fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}

20. Macros

What Are Macros?

Macros are a way of writing code that writes other code, known as metaprogramming. Rust provides a powerful macro system that allows for code generation and reduction of repetitive code.

Declarative Macros

// Basic macro definition
macro_rules! say_hello {
    () => {
        println!("Hello!");
    };
}

// Macro with parameters
macro_rules! create_function {
    ($func_name:ident) => {
        fn $func_name() {
            println!("You called {:?}()", stringify!($func_name));
        }
    };
}

Procedural Macros

use proc_macro;

// Derive macro
#[proc_macro_derive(MyTrait)]
pub fn my_trait_derive(input: TokenStream) -> TokenStream {
    // Implementation
}

// Attribute macro
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
    // Implementation
}

// Function-like macro
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
    // Implementation
}

Common Built-in Macros

// println! macro
println!("Hello, {}!", "world");

// vec! macro
let v = vec![1, 2, 3];

// assert! macro family
assert!(true);
assert_eq!(2 + 2, 4);
assert_ne!(2 + 2, 5);

Topic Details

Definition

The foundation of any Rust development environment. Proper installation ensures access to:

Key Concepts

Cargo is Rust's built-in package manager and build system. It handles:

Definition

Understanding the basics of Rust is crucial for writing efficient and safe code. It introduces:

Key Concepts

Rust emphasizes immutability by default, ensuring safer code. Shadowing allows reusing variable names for transformations.

Definition

Data types define how data is stored and manipulated in Rust. They ensure type safety and performance.

Key Concepts

Rust is statically typed, meaning all variables must have a known type at compile time. Compound types allow grouping multiple values.

Definition

String handling is essential for text processing in Rust. It introduces:

Key Concepts

String slices are lightweight and immutable, while String is growable and heap-allocated.

Definition

Ownership defines how memory is managed in Rust, while borrowing allows temporary access to data without transferring ownership.

Why Important?

Ownership is a core concept in Rust that ensures memory safety without a garbage collector. It is essential for:

Key Concepts

Rust's ownership system is based on three rules:

Benefits

Definition

Lifetimes ensure that references are valid as long as they are used, preventing dangling references.

Key Concepts

Lifetimes are inferred in most cases, but explicit annotations are needed for complex scenarios.

Definition

Control flow structures allow decision-making and iteration in Rust programs.

Why Important?

Control flow structures enable programs to execute different code paths based on conditions, making them dynamic and responsive.

Key Concepts

Benefits

Definition

Functions are reusable named blocks of code, while closures are anonymous functions that can capture variables from their environment.

Why Important?

Functions and closures are the building blocks of Rust programs, enabling modular and reusable code.

Key Concepts

Benefits

Definition

Structs are custom data types that group related data together, enabling encapsulation and improved code organization.

Why Important?

Structs are fundamental for defining custom data types in Rust. They enable:

Key Concepts

Structs in Rust come in three forms:

Methods and associated functions can be implemented for structs using the impl block.

Benefits

Definition

Enums define a type by enumerating its possible variants, while pattern matching enables exhaustive handling of enum variants and other data structures.

Why Important?

Enums allow defining a type by enumerating its possible variants. They are crucial for:

Key Concepts

Enums in Rust can:

The Option and Result enums are built-in examples of enums in Rust.

Benefits

Definition

Error handling ensures robust and predictable programs by addressing potential issues at compile time or runtime.

Why Important?

Error handling in Rust ensures robust and predictable programs. It is a core feature of the language, designed to help developers write safe and reliable code by addressing potential issues at compile time or runtime.

Key Concepts

Benefits

Definition

Common collections are data structures that allow efficient storage and manipulation of groups of data.

Why Important?

Collections are essential for managing groups of data in Rust.

Key Concepts

Rust's collections are efficient and provide methods for iteration and manipulation.

Benefits

Definition

Testing ensures code correctness and prevents regressions by verifying expected behavior.

Why Important?

Testing ensures code correctness and prevents regressions.

Key Concepts

Rust's testing framework is built-in and supports assertions and test organization.

Benefits

Definition

Modules are used to organize code into logical groups, providing encapsulation and avoiding naming conflicts.

Why Important?

Modules and packages enable organizing and reusing code in Rust projects. They are essential for building scalable, maintainable, and reusable software systems.

Key Concepts

Benefits

Definition

Asynchronous programming allows non-blocking execution of tasks, enabling efficient handling of multiple tasks concurrently.

Why Important?

Asynchronous programming is essential for building high-performance, scalable applications that handle multiple tasks concurrently without blocking the main thread.

Key Concepts

Benefits

Definition

Synchronous programming executes tasks sequentially, ensuring one task completes before the next begins.

Why Important?

Synchronous programming is straightforward and easy to reason about, making it suitable for simpler applications or tasks that do not require concurrency.

Key Concepts

Benefits

Definition

Concurrency allows multiple tasks to run simultaneously, improving performance and responsiveness.

Why Important?

Concurrency is essential for building high-performance, responsive applications that can handle multiple tasks simultaneously.

Key Concepts

Benefits

Definition

Threading allows multiple threads of execution to run in parallel, leveraging multiple CPU cores.

Why Important?

Threading is essential for building applications that leverage multiple CPU cores for parallel execution.

Key Concepts

Benefits

Definition

Traits define shared behavior between types, enabling polymorphism and code reuse.

Why Important?

Traits are fundamental for writing flexible and reusable code in Rust. They enable:

Key Concepts

Benefits

Definition

Macros are a metaprogramming feature that allows writing code that generates other code.

Why Important?

Macros provide powerful metaprogramming capabilities that enable:

Key Concepts

Benefits