Rust Scope

Understanding variable visibility and lifetime in Rust

🔍 What is Scope in Rust?

Scope determines where variables are valid and accessible in your code. In Rust, scope is defined by curly braces and controls both variable visibility and memory management through ownership.


fn main() {
    let outer = "I'm in main scope";
    
    {
        let inner = "I'm in inner scope";
        println!("{}", outer); // ✅ Can access outer
        println!("{}", inner); // ✅ Can access inner
    }
    
    println!("{}", outer); // ✅ Still accessible
    // println!("{}", inner); // ❌ Error: inner is out of scope
}
                                    

Output:

I'm in main scope
I'm in inner scope
I'm in main scope

Scope Types

🏠

Block Scope

Variables inside curly braces

{
    let x = 5;
    // x is valid here
} // x goes out of scope
🔧

Function Scope

Variables inside functions

fn my_function() {
    let y = 10;
    // y is valid in this function
}
🌐

Global Scope

Variables accessible everywhere

static GLOBAL: i32 = 100;
// Accessible from anywhere
👥

Shadowing

Redefining variables in scope

let x = 5;
let x = x + 1; // shadows previous x

🔹 Block Scope Basics

Variables are only valid within the block where they're defined:

fn main() {
    let a = 1;
    
    {
        let b = 2;
        {
            let c = 3;
            println!("Inner: a={}, b={}, c={}", a, b, c);
        } // c goes out of scope here
        
        println!("Middle: a={}, b={}", a, b);
        // println!("{}", c); // ❌ Error: c is not in scope
    } // b goes out of scope here
    
    println!("Outer: a={}", a);
    // println!("{}", b); // ❌ Error: b is not in scope
}

Output:

Inner: a=1, b=2, c=3
Middle: a=1, b=2
Outer: a=1

🔹 Function Scope

Each function has its own scope:

fn function_a() {
    let x = "Function A";
    println!("In {}: x = {}", "function_a", x);
}

fn function_b() {
    let x = "Function B"; // Different x, same name
    println!("In {}: x = {}", "function_b", x);
}

fn main() {
    let x = "Main function";
    
    println!("In main: x = {}", x);
    function_a();
    function_b();
    println!("Back in main: x = {}", x);
}

Output:

In main: x = Main function
In function_a: x = Function A
In function_b: x = Function B
Back in main: x = Main function

🔹 Variable Shadowing

You can create new variables with the same name in the same scope:

fn main() {
    let x = 5;
    println!("First x: {}", x);
    
    let x = x + 1; // Shadows the previous x
    println!("Second x: {}", x);
    
    let x = x * 2; // Shadows again
    println!("Third x: {}", x);
    
    {
        let x = "Now I'm a string!"; // Shadows in inner scope
        println!("Inner x: {}", x);
    } // Inner x goes out of scope
    
    println!("Back to outer x: {}", x); // Back to the number
}

Output:

First x: 5
Second x: 6
Third x: 12
Inner x: Now I'm a string!
Back to outer x: 12

🔹 Scope and Ownership

Scope is closely related to Rust's ownership system:

fn main() {
    {
        let s = String::from("Hello"); // s comes into scope
        println!("{}", s);
    } // s goes out of scope and is automatically cleaned up
    
    // println!("{}", s); // ❌ Error: s is no longer in scope
    
    let s1 = String::from("World");
    {
        let s2 = s1; // s1 is moved to s2
        println!("{}", s2);
    } // s2 goes out of scope, memory is cleaned up
    
    // println!("{}", s1); // ❌ Error: s1 was moved
}

Output:

Hello
World

🔹 Practical Scope Example

Using scope to manage temporary calculations:

fn calculate_average(numbers: &[i32]) -> f64 {
    let sum = {
        let mut total = 0;
        for # in numbers {
            total += num;
        }
        total // Return total from this block
    }; // total goes out of scope here
    
    let count = numbers.len() as f64;
    sum as f64 / count
}

fn main() {
    let scores = [85, 92, 78, 96, 88];
    let average = calculate_average(&scores);
    
    println!("Scores: {:?}", scores);
    println!("Average: {:.1}", average);
}

Output:

Scores: [85, 92, 78, 96, 88]
Average: 87.8

🔹 Scope Best Practices

✅ Good Practices:

  • Minimize scope: Declare variables as close to their use as possible
  • Use blocks: Create temporary scopes for calculations
  • Meaningful names: Even with shadowing, use clear variable names
  • Avoid deep nesting: Too many nested scopes can be confusing

⚠️ Common Mistakes:

  • Trying to use variables outside their scope
  • Confusing shadowing with mutation
  • Creating unnecessarily wide scopes

🧠 Test Your Knowledge

What happens when a variable goes out of scope in Rust?