3. Data Structures and Types#

Learn how to organize and structure your data effectively.

3.1. How do I define my own custom data types, like struct in C++ or classes in Python?#

You can group related data fields together into a single type, just like structs in C++ or classes in Python.

Grouping related data:

struct Person {
    name: String,
    age: u32,
    email: String,
}

fn main() {
    let person = Person {
        name: String::from("Alice"),
        age: 30,
        email: String::from("alice@example.com"),
    };

    println!("Name: {}", person.name);
    println!("Age: {}", person.age);
    println!("Email: {}", person.email);
}

Compare to C++:

struct Person {
    std::string name;
    unsigned int age;
    std::string email;
};

int main() {
    Person person = {"Alice", 30, "alice@example.com"};
    std::cout << "Name: " << person.name << std::endl;
}

Compare to Python:

class Person:
    def __init__(self, name, age, email):
        self.name = name
        self.age = age
        self.email = email

person = Person("Alice", 30, "alice@example.com")
print(f"Name: {person.name}")

Creating different choices for a type:

Sometimes you need a type that can be one of several different things. Like a traffic light that can be red, yellow, or green.

enum Status {
    Active,
    Inactive,
    Pending,
}

enum Message {
    Text(String),
    Image(String, u32, u32),  // filename, width, height
    Video { file: String, duration: u32 },
}

fn main() {
    let status = Status::Active;

    let msg = Message::Text(String::from("Hello!"));

    match msg {
        Message::Text(content) => println!("Text: {}", content),
        Message::Image(file, w, h) => println!("Image: {} ({}x{})", file, w, h),
        Message::Video { file, duration } => println!("Video: {} ({}s)", file, duration),
    }
}

Compare to C++ enum classes:

enum class Status {
    Active,
    Inactive,
    Pending
};

// C++ doesn't have built-in variant types like this
// You'd need std::variant or unions

Compare to Python:

from enum import Enum

class Status(Enum):
    ACTIVE = 1
    INACTIVE = 2
    PENDING = 3

# Python doesn't have built-in variant types
# You might use inheritance or type annotations

These grouped data types are called struct and the choice types are called enum.

3.2. How can I store collections of items like lists, arrays, or maps?#

Dynamic lists (like Python lists or C++ vectors):

fn main() {
    // Create an empty list
    let mut numbers = Vec::new();
    numbers.push(1);
    numbers.push(2);
    numbers.push(3);

    // Or create with initial values
    let fruits = vec!["apple", "banana", "orange"];

    // Access items
    println!("First fruit: {}", fruits[0]);

    // Iterate through items
    for fruit in &fruits {
        println!("Fruit: {}", fruit);
    }

    // Get length
    println!("We have {} fruits", fruits.len());
}

Compare to Python:

# Python lists
numbers = []
numbers.append(1)
numbers.append(2)

fruits = ["apple", "banana", "orange"]
print(f"First fruit: {fruits[0]}")

for fruit in fruits:
    print(f"Fruit: {fruit}")

Compare to C++:

#include <vector>
std::vector<int> numbers;
numbers.push_back(1);
numbers.push_back(2);

std::vector<std::string> fruits = {"apple", "banana", "orange"};
std::cout << "First fruit: " << fruits[0] << std::endl;

for (const auto& fruit : fruits) {
    std::cout << "Fruit: " << fruit << std::endl;
}

Key-value pairs (like Python dictionaries or C++ maps):

use std::collections::HashMap;

fn main() {
    // Create an empty map
    let mut scores = HashMap::new();
    scores.insert("Alice", 95);
    scores.insert("Bob", 87);
    scores.insert("Carol", 92);

    // Access values
    match scores.get("Alice") {
        Some(score) => println!("Alice's score: {}", score),
        None => println!("Alice not found"),
    }

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

    // Check if key exists
    if scores.contains_key("Bob") {
        println!("Bob is in the scores");
    }
}

Compare to Python:

# Python dictionaries
scores = {}
scores["Alice"] = 95
scores["Bob"] = 87

# Or create with initial values
scores = {"Alice": 95, "Bob": 87, "Carol": 92}

print(f"Alice's score: {scores['Alice']}")

for name, score in scores.items():
    print(f"{name}: {score}")

Compare to C++:

#include <map>
std::map<std::string, int> scores;
scores["Alice"] = 95;
scores["Bob"] = 87;

// Access with safety check
auto it = scores.find("Alice");
if (it != scores.end()) {
    std::cout << "Alice's score: " << it->second << std::endl;
}

for (const auto& pair : scores) {
    std::cout << pair.first << ": " << pair.second << std::endl;
}

These dynamic lists are called Vec and the key-value collections are called HashMap.

3.3. How do I handle situations where a value might or might not be present, like nullptr in C++ or None in Python?#

Instead of using null pointers that can cause crashes, this language uses a special type that explicitly represents “something” or “nothing”.

fn find_person(name: &str) -> Option<Person> {
    if name == "Alice" {
        Some(Person {
            name: String::from("Alice"),
            age: 30,
            email: String::from("alice@example.com"),
        })
    } else {
        None  // Person not found
    }
}

fn main() {
    let result = find_person("Alice");

    // Safe way to handle the result
    match result {
        Some(person) => println!("Found: {}", person.name),
        None => println!("Person not found"),
    }

    // Alternative shorter syntax
    if let Some(person) = find_person("Alice") {
        println!("Found: {}", person.name);
    }

    // Get value with default
    let person = find_person("Bob").unwrap_or(Person {
        name: String::from("Unknown"),
        age: 0,
        email: String::from(""),
    });
}

Compare to C++ (dangerous):

Person* find_person(const std::string& name) {
    if (name == "Alice") {
        return new Person{"Alice", 30, "alice@example.com"};
    }
    return nullptr;  // Danger: easy to forget to check
}

int main() {
    Person* person = find_person("Alice");
    if (person != nullptr) {  // Must remember to check
        std::cout << "Found: " << person->name << std::endl;
        delete person;  // Must remember to free
    }
}

Compare to Python:

def find_person(name):
    if name == "Alice":
        return Person("Alice", 30, "alice@example.com")
    return None  # Could cause AttributeError if not checked

person = find_person("Alice")
if person is not None:
    print(f"Found: {person.name}")

The compiler forces you to handle both cases (something and nothing). This prevents:

  • Null pointer crashes

  • AttributeError in Python

  • Segmentation faults from accessing invalid memory

You cannot accidentally use a “nothing” value without checking first.

This type is called Option enum.