» Quick Introduction to Rust » 3. Std Library » 3.1 Std Types

Std Types

Box

All values in Rust are stack allocated by default. Values can be boxed (allocated on the heap) by creating a Box<T>. A box is a smart pointer to a heap allocated value of type T.

Boxed values can be dereferenced using the * operator.

// Define a struct to represent a person
struct Person {
    name: String,
    age: u32,
}

// A function that creates a Boxed Person
fn create_person(name: &str, age: u32) -> Box<Person> {
    let person = Person {
        name: String::from(name),
        age,
    };

    // Box the person and return the Box
    Box::new(person)
}

fn main() {
    // Create a boxed person
    let boxed_person = create_person("Alice", 30);

    // Access the fields through dereferencing
    println!("Name: {}, Age: {}", boxed_person.name, (*boxed_person).age);
    // Name: Alice, Age: 30
    // The memory for the person is automatically deallocated when the box goes out of scope
}

Vectors

Vectors are resizable arrays. Like slices, their size is not known at compile time, but they can grow or shrink at any time.

fn main() {
    // Creating a vector with initial values
    let mut numbers: Vec<i32> = vec![1, 2, 3, 4, 5];

    // Accessing elements
    println!("Original Vector: {:?}", numbers);

    // Adding an element to the vector
    numbers.push(6);
    println!("After pushing 6: {:?}", numbers);

    // Removing the last element
    let popped = numbers.pop();
    println!("Popped element: {:?}", popped);
    println!("After popping: {:?}", numbers);

    // Iterating over the vector
    println!("Doubling each element:");
    for num in &mut numbers {
        *num *= 2;
        println!("{}", num);
    }

    // Creating a new vector and concatenating it with the original vector
    let more_numbers = vec![7, 8, 9];
    numbers.extend(more_numbers);
    println!("After extending with more_numbers: {:?}", numbers);

    // Using the `contains` method
    let contains_8 = numbers.contains(&8);
    println!("Does the vector contain 8? {}", contains_8);

    // Creating a vector of squares using the `map` method
    let squares: Vec<i32> = numbers.iter().map(|&x| x * x).collect();
    println!("Vector of squares: {:?}", squares);
}

Strings

There are two types of strings in Rust: String and &str.

A String is stored as a vector of bytes (Vec<u8>), which is heap allocated, growable and mutable.

fn main() {
    // Creating a new empty String
    let mut my_string = String::new();

    // Appending a literal string
    my_string.push_str("Hello, ");

    // Appending another String
    let name = "Alice";
    my_string.push_str(name);

    // Displaying the result
    println!("String: {}", my_string);

    // Converting a String to a &str for borrowing
    let borrowed_str: &str = &my_string;
    println!("Borrowed &str: {}", borrowed_str);
}

&str is a slice (&[u8]) that always points to a valid UTF-8 sequence, which can reference a portion of a String, a string literal, or another &str.

fn main() {
    // Creating a string literal
    let hello_str: &str = "Hello, ";

    // Creating another string literal
    let name_str: &str = "Bob";

    // Concatenating string literals to form a new &str
    let greeting: &str = hello_str.to_owned() + name_str;

    // Displaying the result
    println!("Concatenated &str: {}", greeting);
}

String Escapes

There are multiple ways to write string literals with special characters in them. Generally special characters are escaped with a backslash character: \.

fn main() {
    let orig_str = "Escapes work here: \x3F \u{211D}";
    println!("{}", orig_str);

    let raw_str = r"Escapes don't work here: \x3F \u{211D}";
    println!("{}", raw_str);

    // If you need quotes in a raw string, add a pair of #s
    let quotes = r#"He said: "It's impossible!""#;
    println!("{}", quotes);

    // If you need "# in your string, just use more #s in the delimiter.
    let longer_delimiter = r###"It's a hashtag "#rust in it. And even "##!"###;
    println!("{}", longer_delimiter);
}

Option

The Option<T> enum has two variants:

  • Some(value): a tuple struct that has a value with type T
  • None: lack of value
fn find_even_number(numbers: &[u32]) -> Option<u32> {
    for &num in numbers {
        if num % 2 == 0 {
            return Some(num);
        }
    }
    None
}

fn main() {
    let numbers = vec![1, 3, 5, 2, 8, 9];
    match find_even_number(&numbers) {
        Some(even_num) => println!("Found even number: {}", even_num),
        None => println!("No even number found."),
    }
}

Result

The Result<T, E> enum has two variants:

  • Ok(value): it has a value with type T
  • Err(reason): it explains the cause of the failure with the reason, which has type E
// Function that returns a Result<u32, String> based on a condition
fn divide(a: u32, b: u32) -> Result<u32, String> {
    if b == 0 {
        Err(String::from("Cannot divide by zero"))
    } else {
        Ok(a / b)
    }
}

fn main() {
    let result2 = divide(8, 0);
    match result2 {
        Ok(quotient) => println!("Result: Quotient is {}", quotient),
        Err(error) => println!("Result: Error - {}", error),
    }
}

The ? operator is a shorthand for propagating errors in Rust. It can be used in functions that return a Result, allowing for more concise error handling.

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

fn read_file_contents(file_path: &str) -> Result<String, io::Error> {
    // Attempt to open the file
    let mut file = File::open(file_path)?;

    // Read the contents of the file into a String
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;

    Ok(contents)
}

panic!

In Rust, the panic! macro is used to cause the current thread to panic, which results in unwinding the stack and potentially terminating the program. Panicking is typically reserved for unrecoverable errors or situations where continuing execution would lead to undefined behavior.

fn main() {
    let divisor = 0;
    if divisor == 0 {
        // Panicking if attempting to divide by zero
        panic!("Attempted to divide by zero!");
    }

    // This code will not be reached if panic occurs
    let result = 42 / divisor;
    println!("Result: {}", result);
}

HashMap

In Rust, you can use the HashMap data structure from the std::collections module to store key-value pairs.

use std::collections::HashMap;

fn main() {
    // Creating an empty HashMap
    let mut my_map = HashMap::new();

    // Inserting key-value pairs into the HashMap
    my_map.insert("one", 1);
    my_map.insert("two", 2);
    my_map.insert("three", 3);
    my_map.insert("literank", 58);

    // Accessing values by key
    match my_map.get("two") {
        Some(&value) => println!("The value of 'two' is: {}", value),
        None => println!("Key 'two' not found."),
    }

    // Updating a value
    my_map.insert("three", 30);

    // Iterating over the HashMap
    println!("Iterating over the HashMap:");
    for (key, value) in &my_map {
        println!("Key: {}, Value: {}", key, value);
    }

    // Removing a key-value pair
    my_map.remove("literank");

    // Checking if a key exists
    if my_map.contains_key("literank") {
        println!("Key 'literank' is present.");
    } else {
        println!("Key 'literank' is not present.");
    }
}

HashSet

The HashSet data structure, provided by the std::collections module, allows you to store a collection of unique values.

use std::collections::HashSet;

fn main() {
    // Creating an empty HashSet
    let mut my_set = HashSet::new();

    // Inserting elements into the HashSet
    my_set.insert("apple");
    my_set.insert("banana");
    my_set.insert("orange");

    // Checking if an element is in the HashSet
    if my_set.contains("banana") {
        println!("The set contains 'banana'.");
    } else {
        println!("The set does not contain 'banana'.");
    }

    // Removing an element from the HashSet
    my_set.remove("apple");

    // Iterating over the HashSet
    println!("Iterating over the HashSet:");
    for fruit in &my_set {
        println!("Fruit: {}", fruit);
    }

    // Clearing the HashSet
    my_set.clear();

    // Checking if the HashSet is empty
    if my_set.is_empty() {
        println!("The HashSet is empty.");
    } else {
        println!("The HashSet is not empty.");
    }
}

Rc (Reference Counting)

Rc (Reference Counting) is a type in the std::rc module that provides shared ownership of a value with reference counting.

use std::rc::Rc;

// Define a struct to represent a person
#[derive(Debug)]
struct Person {
    name: String,
    age: u32,
}

fn main() {
    // Creating an Rc containing a Person
    let person = Rc::new(Person {
        name: String::from("Alice"),
        age: 30,
    });

    // Creating multiple references to the Rc
    let reference1 = Rc::clone(&person);
    let reference2 = Rc::clone(&person);

    // Printing the reference counts
    println!("Reference Count after creation: {}", Rc::strong_count(&person));

    // Accessing and printing the data through references
    println!("Person: {:?}", *reference1);
    println!("Person: {:?}", *reference2);

    // Dropping one reference
    drop(reference1);
    println!("Reference Count after dropping one reference: {}", Rc::strong_count(&person));

    // Dropping the last reference
    drop(reference2);
    // The Rc is automatically deallocated when the last reference is dropped

    // Attempting to access the data after all references are dropped will result in a compile error
}

Arc (Atomic Reference Counting)

A thread-safe reference-counting pointer. The type Arc<T> provides shared ownership of a value of type T, allocated in the heap.

Arc is useful when you need shared ownership across multiple threads and want to ensure thread safety. It uses atomic operations for reference counting, making it suitable for concurrent programming. For Example:

use std::sync::Arc;
use std::thread;

// Define a struct to represent a person
#[derive(Debug)]
struct Person {
    name: String,
    age: u32,
}

fn main() {
    // Creating an Arc containing a Person
    let person = Arc::new(Person {
        name: String::from("Bob"),
        age: 25,
    });

    // Creating a reference to the Arc in a new thread
    let thread_person = Arc::clone(&person);

    // Spawn a new thread
    let handle = thread::spawn(move || {
        // Accessing and printing the data through the reference in the new thread
        println!("Person in the new thread: {:?}", *thread_person);
    });

    // Accessing and printing the data in the main thread
    println!("Person in the main thread: {:?}", *person);

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

    // After the thread has joined, the Arc is automatically deallocated when the last reference is dropped
}

Code Challenge

Modify the code in the editor and try to get Alice's grades.

Loading...
> code result goes here
Prev
Next