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 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:
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 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:
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:
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:
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