Rust Concurrency and Error Handling

by mahidhar

Sure, let's continue exploring some additional advanced topics in Rust programming, including Concurrency, Functional Programming, and Error Handling.

Concurrency:

Rust's ownership system makes it easier to write concurrent programs by preventing data races at compile time. Concurrency in Rust can be achieved through threads, async programming, and channels.

Threads:

Simple Example:

code
use std::thread;

fn main() {
    let handle = thread::spawn(|| {
        println!("Hello from a thread!");
    });

    handle.join().unwrap();
}

Complex Example:

code
use std::thread;

fn main() {
    let cities = vec!["New York", "Los Angeles", "Chicago"];

    let handles: Vec<_> = cities.into_iter().map(|city| {
        thread::spawn(move || {
            println!("Processing city: {}", city);
        })
    }).collect();

    for handle in handles {
        handle.join().unwrap();
    }
}
Channels:

Channels provide a way for threads to communicate with each other.

Simple Example:

code
use std::sync::mpsc;
use std::thread;

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

    thread::spawn(move || {
        tx.send("Hello from a thread!").unwrap();
    });

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

Complex Example:

code
use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn main() {
    let (tx, rx) = mpsc::channel();
    let cities = vec!["New York", "Los Angeles", "Chicago"];

    for city in cities {
        let tx = tx.clone();
        thread::spawn(move || {
            thread::sleep(Duration::from_secs(1));
            tx.send(city).unwrap();
        });
    }

    for received in rx.iter().take(3) {
        println!("Processing city: {}", received);
    }
}
Async/Await:

Rust’s async/await syntax provides a powerful way to write asynchronous code.

Simple Example:

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

#[tokio::main]
async fn main() {
    println!("Start");
    sleep(Duration::from_secs(1)).await;
    println!("End");
}

Complex Example:

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

async fn process_city(city: &str) {
    println!("Processing city: {}", city);
    sleep(Duration::from_secs(1)).await;
    println!("Finished processing: {}", city);
}

#[tokio::main]
async fn main() {
    let cities = vec!["New York", "Los Angeles", "Chicago"];

    let tasks: Vec<_> = cities.into_iter().map(|city| {
        task::spawn(process_city(city))
    }).collect();

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

Functional Programming:

Rust supports many functional programming concepts, such as higher-order functions, iterators, and closures.

Higher-Order Functions:

Simple Example:

code
fn apply<F>(f: F, value: i32) -> i32
where
    F: Fn(i32) -> i32,
{
    f(value)
}

fn main() {
    let double = |x| x * 2;
    let result = apply(double, 5);
    println!("Result: {}", result);
}

Complex Example:

code
fn apply_to_cities<F>(f: F, cities: Vec<&str>) -> Vec<String>
where
    F: Fn(&str) -> String,
{
    cities.into_iter().map(f).collect()
}

fn main() {
    let add_prefix = |city: &str| format!("City: {}", city);
    let cities = vec!["New York", "Los Angeles", "Chicago"];
    let result = apply_to_cities(add_prefix, cities);

    for city in result {
        println!("{}", city);
    }
}
Iterators:

Iterators in Rust are powerful and flexible, providing a variety of methods to process sequences of elements.

Simple Example:

code
fn main() {
    let cities = vec!["New York", "Los Angeles", "Chicago"];
    for city in cities.iter() {
        println!("City: {}", city);
    }
}

Complex Example:

code
fn main() {
    let cities = vec!["New York", "Los Angeles", "Chicago"];
    let uppercase_cities: Vec<_> = cities
        .into_iter()
        .map(|city| city.to_uppercase())
        .collect();

    for city in uppercase_cities {
        println!("City: {}", city);
    }
}
Closures:

Closures in Rust are anonymous functions you can save in a variable or pass as arguments to other functions.

Simple Example:

code
fn main() {
    let greet = |name| println!("Hello, {}!", name);
    greet("Tokyo");
}

Complex Example:

code
fn main() {
    let cities = vec!["New York", "Los Angeles", "Chicago"];
    let filter_large_cities = |city: &&str| city.len() > 6;

    let large_cities: Vec<_> = cities.iter().filter(filter_large_cities).collect();

    for city in large_cities {
        println!("Large city: {}", city);
    }
}

Error Handling:

Rust's approach to error handling is through Result and Option types, encouraging safe and explicit error management.

Result:

Simple Example:

code
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, 2.0) {
        Ok(result) => println!("Result: {}", result),
        Err(e) => println!("Error: {}", e),
    }
}

Complex Example:

code
use std::fs::File;
use std::io::{self, Read};

fn read_file(path: &str) -> Result<String, io::Error> {
    let mut file = File::open(path)?;
    let mut content = String::new();
    file.read_to_string(&mut content)?;
    Ok(content)
}

fn main() {
    match read_file("cities.txt") {
        Ok(content) => println!("File content: {}", content),
        Err(e) => println!("Error: {}", e),
    }
}
Option:

Simple Example:

code
fn find_city(cities: Vec<&str>, query: &str) -> Option<&str> {
    for city in cities {
        if city == query {
            return Some(city);
        }
    }
    None
}

fn main() {
    let cities = vec!["New York", "Los Angeles", "Chicago"];
    match find_city(cities, "Chicago") {
        Some(city) => println!("Found city: {}", city),
        None => println!("City not found"),
    }
}

Complex Example:

code
fn get_city_population(city: &str) -> Option<u32> {
    match city {
        "New York" => Some(8_336_817),
        "Los Angeles" => Some(3_979_576),
        "Chicago" => Some(2_693_976),
        _ => None,
    }
}

fn main() {
    let city = "San Francisco";
    match get_city_population(city) {
        Some(population) => println!("The population of {} is {}", city, population),
        None => println!("Population data not available for {}", city),
    }
}

These additional sections delve deeper into concurrency, functional programming, and error handling in Rust, providing both simple and complex examples to illustrate each concept. This should give you a comprehensive understanding of Rust's advanced capabilities and how to leverage them in real-world applications.