Rust LogoCommon Collections

In Rust, common collections are data structures that store multiple values. Unlike built-in array types with a fixed size, these collections can grow or shrink in size during runtime. They are essential for most applications as they provide flexible ways to organize and manipulate data. Rust's standard library provides three primary collections that are widely used:

1. Vectors (`Vec<T>`):
* Purpose: Vectors store a variable number of values of the same type next to each other in memory. They are similar to dynamic arrays or ArrayLists in other languages.
* Characteristics:
* Growable: Can add or remove elements dynamically.
* Homogeneous: All elements must be of the same type `T`.
* Contiguous Memory: Elements are stored sequentially in memory, which can lead to good cache performance.
* Ownership: When a vector goes out of scope, all its elements are dropped, and its memory is deallocated.
* Common Operations: Creating, adding elements (`push`), accessing elements (`[]` for unchecked access or `get()` for checked access returning `Option<&T>`), iterating.

2. Strings (`String`):
* Purpose: The `String` type is a growable, mutable, owned, UTF-8 encoded string type. It's the standard library's heap-allocated string type.
* Characteristics:
* Owned: `String` owns its data, meaning it can be modified.
* Growable: Can append characters or other strings.
* UTF-8 Encoded: Can store any valid UTF-8 sequence, making it suitable for international text.
* Difference from `&str`: `&str` (string slice) is an immutable reference to a string, typically used for fixed-size string literals or parts of `String`s.
* Common Operations: Creating (`String::new()`, `to_string()`, `String::from()`), appending (`push_str`, `push`), concatenating (`+` operator, `format!` macro), iterating over characters or bytes.

3. Hash Maps (`HashMap<K, V>`):
* Purpose: Hash maps store mappings from keys of type `K` to values of type `V`. They are useful when you want to look up data by a key rather than by an index.
* Characteristics:
* Key-Value Pairs: Each element is a pair consisting of a key and its associated value.
* Unique Keys: Each key must be unique within a `HashMap`.
* Hashable Keys: Keys must implement the `Eq` and `Hash` traits (which means most primitive types like integers, floats, and strings can be keys).
* No Guaranteed Order: The order of key-value pairs is not guaranteed and can change.
* Performance: Offers average O(1) time complexity for insertions and lookups, but can degrade to O(n) in worst-case scenarios (hash collisions).
* Common Operations: Creating (`HashMap::new()`), inserting key-value pairs (`insert`), retrieving values (`get`), iterating over key-value pairs, updating values (`entry().or_insert()` for conditional insertion/update).

These collections provide flexible and efficient ways to handle dynamic data in Rust, integrating seamlessly with Rust's ownership and borrowing system to ensure memory safety.

Example Code

use std::collections::HashMap;

fn main() {
    // --- 1. Vectors (`Vec<T>`) ---
    println!("\n--- Vectors ---");
    // Create a new, empty vector
    let mut numbers: Vec<i32> = Vec::new();

    // Add elements to the vector using push()
    numbers.push(10);
    numbers.push(20);
    numbers.push(30);

    println!("Initial vector: {:?}", numbers);

    // Access elements by index safely using get() which returns an Option<&T>
    match numbers.get(1) {
        Some(second_element) => println!("Second element: {}", second_element),
        None => println!("There is no second element."),
    }

    // You can also access using `[]`, but it will panic if the index is out of bounds
    // let third_element = numbers[2]; 
    // println!("Third element: {}", third_element);

    // Iterate over elements (immutable reference)
    println!("Iterating over numbers:");
    for num in &numbers {
        println!("  Value: {}", num);
    }

    // Iterate and modify elements (mutable reference)
    for num in &mut numbers {
        *num += 1; // Dereference `num` to change the actual value in the vector
    }
    println!("Vector after mutation: {:?}", numbers);

    // --- 2. Strings (`String`) ---
    println!("\n--- Strings ---");
    // Create a new empty String
    let mut s = String::new();

    // Append a string slice (`&str`)
    s.push_str("Hello");
    s.push_str(", Rust");
    println!("String after push_str: {}", s);

    // Append a single character
    s.push('!');
    println!("String after push char: {}", s);

    // Concatenate Strings using the `+` operator. Note: takes ownership of the left operand.
    let s1 = String::from("World");
    let s2 = String::from("!");
    let s3 = s1 + &s2; // s1 is moved here and can no longer be used
    println!("Concatenated string (s1 + &s2): {}", s3);
    // println!("s1: {}", s1); // This line would cause a compile-time error as s1 was moved

    // Concatenate using `format!` macro (more flexible, does not take ownership of its arguments)
    let name = String::from("Alice");
    let greeting = format!("Hello, {}! Welcome to {}.", name, "the Rust world");
    println!("Formatted string: {}", greeting);
    println!("Original name string still usable: {}", name); // `name` is still valid here

    // Iterate over characters in a String
    println!("Characters in '{}':", greeting);
    for c in greeting.chars() {
        print!("{} ", c);
    }
    println!();

    // --- 3. Hash Maps (`HashMap<K, V>`) ---
    println!("\n--- Hash Maps ---");
    // Create a new, empty HashMap
    let mut scores = HashMap::new();

    // Insert key-value pairs
    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);

    println!("Initial scores: {:?}", scores);

    // Get a value by key using get() which returns an Option<&V>
    let team_name = String::from("Blue");
    match scores.get(&team_name) {
        Some(score) => println!("Score for {}: {}", team_name, score),
        None => println!("{} team not found.", team_name),
    }

    // Iterate over key-value pairs
    println!("Iterating over scores:");
    for (key, value) in &scores {
        println!("  {}: {}", key, value);
    }

    // Overwriting a value for an existing key
    scores.insert(String::from("Blue"), 25);
    println!("Scores after updating Blue: {:?}", scores);

    // Adding a value only if the key doesn't exist (`entry().or_insert()`)
    scores.entry(String::from("Red")).or_insert(30);
    scores.entry(String::from("Yellow")).or_insert(100); // Yellow already exists, so its value won't change
    println!("Scores after entry().or_insert(): {:?}", scores);

    // Accessing and modifying a value using `entry().or_insert()`
    let green_score_ref = scores.entry(String::from("Green")).or_insert(0);
    *green_score_ref += 5; // Modify the value through the mutable reference
    println!("Scores after modifying Green: {:?}", scores);
}