Lesson 13-Rust Basics

Rust Language Basics

Introduction to Rust and Installation

Rust is a systems programming language developed by Mozilla, with its 1.0 version released in 2015, focusing on performance and memory safety. Its ownership system prevents memory issues, making it widely used for systems programming and high-performance applications.

Installing Rust

  • Windows/macOS/Linux:
  curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
  • Verify Installation:
  rustc --version
  cargo --version

Basic Syntax and Hello World

Example Code

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

Compilation and Execution

rustc main.rs
./main   # Windows: main.exe

Breakdown

  • fn main(): Program entry point function.
  • println!: Macro for formatted output.
  • Semicolon: Expressions in Rust end with a semicolon.

Using the Cargo Management Tool

Introduction to Cargo and Basic Commands

Cargo is Rust’s build tool and package manager, simplifying project management and dependency handling.

Basic Commands

  • Create a New Project:
  cargo new my_project
  cd my_project
  • Build:
  cargo build
  • Run:
  cargo run
  • Check Code:
  cargo check

Creating and Managing Projects

Project Structure

my_project/
├── Cargo.toml    # Configuration file
├── src/
│   └── main.rs   # Main file

Cargo.toml Example

[package]
name = "my_project"
version = "0.1.0"
edition = "2021"

[dependencies]

Adding Dependencies

cargo add serde
  • Update Cargo.toml:
  [dependencies]
  serde = "1.0"

Rust Modules and Package Management

Module System Basics

Rust uses modules to organize code.

Example Code

mod utils {
    pub fn add(a: i32, b: i32) -> i32 {
        a + b
    }
}

fn main() {
    let sum = utils::add(3, 4);
    println!("Sum: {}", sum);
}

Breakdown

  • mod: Defines a module.
  • pub: Makes a module or function public.
  • ::: Accesses module members.

File-Based Modules

src/
├── main.rs
├── utils.rs
  • utils.rs:
  pub fn subtract(a: i32, b: i32) -> i32 {
      a - b
  }
  • main.rs:
  mod utils;

  fn main() {
      let result = utils::subtract(5, 2);
      println!("Result: {}", result);
  }

Packages and Dependency Management

Example: Adding an External Dependency

[dependencies]
rand = "0.8.5"
use rand::Rng;

fn main() {
    let num = rand::thread_rng().gen_range(1..=100);
    println!("Random number: {}", num);
}

Breakdown

  • use: Imports external modules.
  • Cargo: Automatically downloads and compiles dependencies.

Rust Functions and Closures

Function Definition and Invocation

Example Code

fn add(a: i32, b: i32) -> i32 {
    a + b // No semicolon indicates return value
}

fn main() {
    let result = add(5, 3);
    println!("Result: {}", result);
}

Breakdown

  • Parameter Types: Explicitly declared.
  • Return Value: Last expression or explicit return.

Closures and Higher-Order Functions

Example Code

fn main() {
    let double = |x: i32| x * 2;
    let numbers = vec![1, 2, 3];
    let doubled: Vec<i32> = numbers.iter().map(|&x| double(x)).collect();
    println!("Doubled: {:?}", doubled);
}

Breakdown

  • |x|: Closure parameters.
  • map: Higher-order function applying the closure.

Rust Ownership and Lifetimes

Ownership Rules

Rust’s ownership system ensures memory safety.

Example Code

fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // s1 is moved, becomes invalid
    // println!("{}", s1); // Error: s1 is no longer usable
    println!("{}", s2);
}

Rules

  1. Each value has a single owner.
  2. When the owner goes out of scope, the value is dropped.
  3. Multiple mutable references cannot exist simultaneously.

Borrowing and Lifetimes

Example Code

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

fn main() {
    let s1 = String::from("short");
    let s2 = String::from("longer");
    let result = longest(&s1, &s2);
    println!("Longest: {}", result);
}

Breakdown

  • &: Borrowing reference.
  • 'a: Lifetime annotation ensuring reference validity.

Structs and Enums

Defining and Using Structs

Example Code

struct Task {
    id: u32,
    text: String,
    done: bool,
}

impl Task {
    fn new(id: u32, text: String) -> Task {
        Task { id, text, done: false }
    }
}

fn main() {
    let task = Task::new(1, String::from("Learn Rust"));
    println!("Task: {} - {}", task.id, task.text);
}

Breakdown

  • struct: Defines a struct.
  • impl: Implements methods for the struct.

Enums and Pattern Matching

Example Code

enum Status {
    Pending,
    Completed(String),
}

fn main() {
    let status = Status::Completed(String::from("Done"));
    match status {
        Status::Pending => println!("Task is pending"),
        Status::Completed(msg) => println!("Task completed: {}", msg),
    }
}

Breakdown

  • enum: Defines an enum.
  • match: Pattern matching to handle variants.

Asynchronous Programming with Tokio

Rust Asynchronous Basics

Rust uses async and await for asynchronous programming.

Example Code

async fn say_hello() -> String {
    "Hello, Rust!".to_string()
}

#[tokio::main]
async fn main() {
    let message = say_hello().await;
    println!("{}", message);
}

Breakdown

  • async fn: Defines an asynchronous function.
  • .await: Waits for an asynchronous result.

Using Tokio for Asynchronous Tasks

Installing Tokio

cargo add tokio --features "full"

Example Code

use tokio::time::{sleep, Duration};

async fn delayed_task(id: i32) {
    sleep(Duration::from_secs(1)).await;
    println!("Task {} completed", id);
}

#[tokio::main]
async fn main() {
    let tasks = vec![
        tokio::spawn(delayed_task(1)),
        tokio::spawn(delayed_task(2)),
    ];

    for task in tasks {
        task.await.unwrap();
    }
}

Breakdown

  • tokio::spawn: Creates an asynchronous task.
  • sleep: Simulates asynchronous delay.

Comprehensive Case Study: Asynchronous Task Manager

Requirements Analysis

Build an asynchronous task manager that:

  • Adds tasks and executes them asynchronously.
  • Displays task status.
  • Uses ownership and structs for task management.

Implementation Code Breakdown

Cargo.toml

[package]
name = "task_manager"
version = "0.1.0"
edition = "2021"

[dependencies]
tokio = { version = "1.37.0", features = ["full"] }

src/main.rs

use tokio::time::{sleep, Duration};
use std::sync::Arc;

#[derive(Debug)]
struct Task {
    id: u32,
    text: String,
    done: bool,
}

impl Task {
    fn new(id: u32, text: String) -> Task {
        Task { id, text, done: false }
    }

    async fn execute(&mut self) {
        sleep(Duration::from_secs(2)).await;
        self.done = true;
        println!("Task {} completed: {}", self.id, self.text);
    }
}

#[tokio::main]
async fn main() {
    let mut tasks = vec![
        Task::new(1, "Learn Rust".to_string()),
        Task::new(2, "Build App".to_string()),
    ];

    let mut handles = Vec::new();
    for task in tasks.iter_mut() {
        let task = Arc::new(tokio::sync::Mutex::new(task));
        let handle = tokio::spawn({
            let task = Arc::clone(&task);
            async move {
                let mut task = task.lock().await;
                task.execute().await;
            }
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.await.unwrap();
    }

    for task in &tasks {
        println!("Task {}: {}", task.id, if task.done { "Done" } else { "Pending" });
    }
}

Running the Application

cargo run

Analysis

  • Struct: Task defines the task structure.
  • Asynchronous: execute method simulates task execution.
  • Arc and Mutex: Ensure safe sharing of tasks in asynchronous contexts.

Advanced Techniques and Considerations

Performance Optimization Tips

  • Avoid Over-Borrowing: Minimize reference complexity.
  • Asynchronous Optimization: Use tokio::select! for concurrent task handling.

Debugging and Error Handling

  • Logging:
  cargo add log env_logger
  env_logger::init();
  log::info!("Starting task {}", id);
  • Error Handling:
  fn might_fail() -> Result<(), String> {
      Ok(())
  }
Share your love