DEV Community

Cover image for Week 2 of Learning Rust: Tuples, Enums & Control Flow
Lymah
Lymah

Posted on

Week 2 of Learning Rust: Tuples, Enums & Control Flow

Hello, fellow Rustaceans (or aspiring ones)!Welcome back to Week 2 of my deep dive into the Rust programming language, and this week I explored compound types and control flow. Here's what I learned, where I got stuck, and how I'm making it make sense.

Here's what I tackled:

- A. Compound Types:

  • String (and its relationship with &str)
  • Arrays
  • Slices
  • Tuples
  • Structs
  • Enums

- B. Control Flow:

  • if/else if/else
  • loop
  • while
  • for loops with iterators

Let's dive into how these building blocks are starting to click for me, and how AI is continuing to be a valuable co-pilot.


A. Compound Types

Last week, we dealt with simple numbers and booleans. This week was all about grouping related data and defining my own custom types.

1. String vs. &str

This was probably the most nuanced part of learning about text.

- String: Owned, mutable, grows on the heap. Think of it as a dynamic, editable document.

- &str (string slice):
An immutable reference to a String(or a string literal that lives in the binary). Think of it as a view or a borrow of part of a document.

Understanding when to use which and how they interact is fundamental for efficient and safe string manipulation in Rust.

fn main() {
    let mut s1 = String::from("Hello"); // Owned, mutable String
    s1.push_str(", world!");
    println!("{}", s1);

    let s_slice: &str = &s1[0..5]; // Immutable slice from s1
    println!("Slice: {}", s_slice);

    let literal_slice: &str = "This is a literal"; // String literal is also &str
    println!("Literal: {}", literal_slice);

    // AI helped clarify conversion:
    // Prompt: "How do I convert &str to String in Rust?"
    let from_slice = String::from(literal_slice);
    println!("Converted to String: {}", from_slice);
}
Enter fullscreen mode Exit fullscreen mode

My AI Assistant Insight: I found myself frequently asking my AI assistant for clarification on String vs. &str scenarios. It quickly provided examples for conversions (.to_string(), String::from()) and explained the performance implications of each, helping me choose the right type for the job.

2. Arrays and Slices: Fixed vs. Dynamic Collections

  • Arrays: Fixed-size collections of elements of the same type, allocated on the stack. Great for compile-time known sizes.

let arr: [i32; 5] = [1, 2, 3, 4, 5];
println!("First element: {}", arr[0]);
// arr[5] would cause a panic at runtime!
Enter fullscreen mode Exit fullscreen mode
  • Slices: A dynamic view into a part of a collection (like an array or Vec). They don't take ownership and are essentially a pointer to the start and a length.
let a = [1, 2, 3, 4, 5];
let slice = &a[1..4]; // slice is &[2, 3, 4]
println!("Slice: {:?}", slice); // Needs Debug trait for printing arrays/slices
Enter fullscreen mode Exit fullscreen mode

Slices are incredibly powerful for passing parts of collections around efficiently without copying.

3. Tuples: Grouping Different Types Together

Tuples are simple, fixed-size collections that can hold values of different types. Useful for returning multiple values from a function.

fn get_user_info() -> (&str, i32, bool) {
    ("Alice", 30, true)
}

fn main() {
    let person: (&str, i32, bool) = ("Bob", 25, false);
    println!("Name: {}, Age: {}", person.0, person.1); // Access by index

    let (name, age, _is_active) = get_user_info(); // Destructuring
    println!("User from function: {}, {}", name, age);
}
Enter fullscreen mode Exit fullscreen mode

4. Structs

Structs are like classes in other languages (without methods, initially), they let you create custom data types with named fields. This is fundamental for organizing related data.

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

fn main() {
    let user1 = User {
        username: String::from("alice123"),
        email: String::from("alice@example.com"),
        active: true,
        sign_in_count: 1,
    };
    println!("User: {}, Email: {}", user1.username, user1.email);

    // AI's help with update syntax:
    // Prompt: "How do I create a new struct instance from an existing one in Rust?"
    let user2 = User {
        email: String::from("bob@example.com"),
        ..user1 // Fills in remaining fields from user1
    };
    println!("User2: {}", user2.username); // User2's username is also alice123
}
Enter fullscreen mode Exit fullscreen mode

5. Enums

Enums are fantastic for representing a set of distinct, related possibilities. Coupled with the match control flow, they become incredibly powerful for handling different states or data variations.

enum Message {
    Quit,
    Move { x: i32, y: i32 }, // Enum variant with anonymous struct
    Write(String), // Enum variant with a String
    ChangeColor(i32, i32, i32), // Enum variant with a tuple
}

fn process_message(msg: Message) {
    match msg {
        Message::Quit => println!("The Quit message was received."),
        Message::Move { x, y } => println!("Move to {} {}", x, y),
        Message::Write(text) => println!("Text message: {}", text),
        Message::ChangeColor(r, g, b) => println!("Change color to R:{} G:{} B:{}", r, g, b),
    }
}

fn main() {
    let m1 = Message::Quit;
    let m2 = Message::Move { x: 10, y: 20 };
    let m3 = Message::Write(String::from("hello Rust"));
    let m4 = Message::ChangeColor(255, 0, 100);

    process_message(m1);
    process_message(m2);
    process_message(m3);
    process_message(m4);
}
Enter fullscreen mode Exit fullscreen mode

B. Control Flow

After defining my data, the next step was to control what my program does and when. Rust's control flow constructs are powerful and familiar, but with some Rust-specific nuances.

1. if/else if/else: Conditional Execution

Pretty standard, but remember that if conditions must evaluate to a bool. Also, if is an expression, which is a neat feature for assigning values conditionally.

fn main() {
    let number = 7;

    if number < 5 {
        println!("condition was true");
    } else {
        println!("condition was false");
    }

    let result = if number % 2 == 0 { "even" } else { "odd" };
    println!("The number is {}", result);
}
Enter fullscreen mode Exit fullscreen mode

2. loop: Infinite Loops (with break and continue)

The loop keyword creates an infinite loop, perfect for retrying operations or listening for events. break exits, continue skips to the next iteration. You can even return values from a loop.

fn main() {
    let mut counter = 0;
    let result = loop {
        counter += 1;
        if counter == 10 {
            break counter * 2; // Returns a value from the loop!
        }
    };
    println!("The result is {}", result); // result will be 20
}
Enter fullscreen mode Exit fullscreen mode

3. while: Conditional Loops

For loops that run as long as a condition is true.

fn main() {
    let mut number = 3;
    while number != 0 {
        println!("{}!", number);
        number -= 1;
    }
    println!("LIFTOFF!!!");
}
Enter fullscreen mode Exit fullscreen mode

5. for Loops

for loops Iterates Over Collections. This is the most common loop for iterating over collections. Rust's for loop works with iterators, which is a very efficient and safe way to process data.

fn main() {
    let a = [10, 20, 30, 40, 50];
    for element in a.iter() { // Iterate over array elements
        println!("The value is: {}", element);
    }

    // Looping with a range
    for number in 1..4 { // Range is exclusive of the end (1, 2, 3)
        println!("{}!", number);
    }

    // AI helped with reverse iteration:
    // Prompt: "How do I loop backwards over a range in Rust?"
    for number in (1..4).rev() { // Reverse range
        println!("{}!", number);
    }
}
Enter fullscreen mode Exit fullscreen mode

Week 2 Reflections

This week felt like I gained a lot of power in organizing and controlling my programs. Understanding String vs. &str and the different compound types like struct and enum is crucial for writing robust Rust. The control flow structures felt familiar from other languages, but seeing them used as expressions (like if statements) was a neat Rust-specific twist.

My AI assistant continues to be a fantastic resource for quick syntax lookups, clarifying nuances (especially with String vs. &str), and discovering idiomatic ways to do things (like .rev() on iterators). It's accelerating my learning without doing the thinking for me, which is exactly the balance I'm aiming for in 2025.

In case you missed week 1, check it out here.

See y'all in week 3.

Top comments (0)