Mutable vs Immutable

1. Welcome and Icebreaker:

Welcome!

If you could make one thing in your life mutable (changeable) and one thing immutable (unchangeable), what would they be and why?

2. Outline the Session:

Here’s the plan:

  1. Concept Explanation: We’ll briefly review the difference between mutable and immutable variables and the keywords let and let mut.
  2. Live Coding Demonstration: We’ll write several code examples to demonstrate the behavior of mutable and immutable variables in different scenarios.
  3. Tasks: You’ll get specific exercises to practice creating, using, and (when appropriate) modifying variables.

Learning Outcomes: By the end of this session, you will be able to:

  • Confidently declare both mutable and immutable variables.
  • Understand the compiler errors that arise from trying to modify an immutable variable.
  • Use mutable variables correctly to update values within a program.
  • Choose between mutable and immutable variables appropriately based on the needs of your code.

Expectations: Experiment with the code! Try to make changes and predict the results. Don’t be afraid to break things – it’s a great way to learn.

3. Concept Explanation:

  • Immutable Variables:
    • Declared using let.
    • Their value cannot be changed after initialization.
    • This is the default in Rust, promoting safer and more predictable code.
    • Attempts to modify an immutable variable result in a compile-time error.
  • Mutable Variables:
    • Declared using let mut.
    • Their value can be changed after initialization.
    • Used when you need to update a value over time.

Why Immutability by Default?

Rust’s emphasis on immutability helps prevent common programming errors, such as:

  • Accidental Modification: You’re less likely to change a value unintentionally.
  • Data Races: In concurrent programming, immutability eliminates a whole class of bugs related to multiple threads accessing and modifying the same data.
  • Easier Reasoning: It’s easier to understand and debug code when you know that certain values will never change.

4. Live Coding Demonstration:

fn main() {
    // Immutable variable
    let x = 5;
    println!("x: {}", x);
    // x = 6; // ERROR: Cannot assign twice to immutable variable `x`

    // Mutable variable
    let mut y = 10;
    println!("y: {}", y);
    y = 15; // This is allowed because y is mutable
    println!("y after modification: {}", y);

    // Shadowing (this is NOT mutation)
    let z = 20;
    let z = z + 5; // This creates a *new* variable named z, shadowing the previous one
    println!("z (shadowed): {}", z);
    let z = "Now a string"; //Shadowing with different type
    println!("z (shadowed): {}", z);

    // Mutability and references
    let mut a = 30;
    let b = &mut a; // Mutable borrow
    *b += 10;       // Modify the value through the mutable borrow
    println!("a: {}", a); // a is now 40

    // Immutable borrow of a mutable variable
    let mut c = 50;
    let d = &c; // Immutable borrow
    // *d = 60;  // ERROR: Cannot assign to `*d` which is behind a `&` reference
    println!("c: {}", c); // c is still 50

    //Multiple Immutable and mutable
     let mut e = 50;
    //let f = &mut e; // ERROR: Cannot assign to `*d` which is behind a `&` reference
    //let g = &mut e; // ERROR: Cannot assign to `*d` which is behind a `&` reference
    let h = &e;
    let i = &e;

     //println!("f: {}", f);
     //println!("g: {}", g);
     println!("h: {}", h);
     println!("i: {}", i);
    println!("e: {}", e);
}

Key Takeaways:

  • let creates immutable variables. Attempts to reassign them cause errors.
  • let mut creates mutable variables, which can be reassigned.
  • Shadowing is not the same as mutation. Shadowing creates a new variable with the same name.
  • Mutable references (&mut) allow you to modify a variable through a borrow.
  • You cannot modify a value through an immutable reference (&).
  • Can not multiple mutable variable.
  • Before mutable variable created, can not have multiple immutable.

5. Tasks

  1. Basic Mutation: Create a mutable variable, initialize it to a number, and then perform a series of arithmetic operations on it (add, subtract, multiply, divide). Print the variable’s value after each operation.
  2. Immutability Challenge: Intentionally try to modify an immutable variable. Observe the compiler error message. Then, change the code to use a mutable variable instead.
  3. Shadowing vs. Mutation: Write a short program that demonstrates the difference between shadowing a variable and mutating a variable. Include comments explaining what’s happening in each case.
  4. Mutable Borrowing and Functions: Create a function that takes a mutable borrow of an integer as an argument. Inside the function, modify the integer (e.g., double its value). Call the function, and then print the original variable to demonstrate that it has been changed.
  5. Immutable Borrowing: Create a function that takes an immutable borrow of a string slice (&str). The function will calculate and print the string’s length, number of characters, or other read-only operation.
  6. Mixed Mutability: Declare both a mutable variable and an immutable variable. Create a mutable borrow of the mutable variable. Try to use the immutable variable while the mutable borrow is still in scope. Observe what happens.
  7. Scope and Mutability: Create a mutable variable within an inner scope (e.g., inside a block defined by curly braces {}). Try to access and modify this variable outside of that inner scope. Observe the compiler error and explain why it occurs.