Rust Scope
Scope is the region of code where a variable is valid. It’s a simple idea, but understanding it makes a lot of Rust’s other rules (especially ownership) click into place.
Scope is defined by curly braces
Section titled “Scope is defined by curly braces”A variable exists from the point it’s declared until the closing } of the block it’s in:
fn main() { let x = 5; // x comes into scope here
{ let y = 10; // y comes into scope here println!("x = {x}, y = {y}"); // both are valid } // y goes out of scope here
println!("x = {x}"); // println!("{y}"); // ❌ error: y is no longer in scope}Output:
x = 5, y = 10x = 5y only exists inside the inner { } block. Once that block ends, y is gone — trying to use it afterward is a compile error, not a runtime surprise.
Scope only goes one way: an inner block can see the variables of the blocks around it (that’s how the inner block prints x), but an outer block can’t see into an inner one.
Function bodies are a scope too
Section titled “Function bodies are a scope too”The parameters and any variables you create inside a function only exist for the duration of that function call:
fn calculate() -> i32 { let result = 5 * 2; // result only exists inside calculate result}
fn main() { let value = calculate(); println!("{value}"); // println!("{result}"); // ❌ error: result doesn't exist out here}Output:
10The same is true the other way round: a function can’t see the local variables of main. If it needs a value, you pass it in as a parameter, as you saw on the Functions page.
Blocks are expressions too
Section titled “Blocks are expressions too”A { } block doesn’t just limit where variables live. It can also produce a value: the last expression inside it, written without a semicolon. That’s the same rule that makes a function return its last line.
This is handy when you need a few helper variables to work something out, but don’t want them hanging around afterwards:
fn main() { let price = 40;
let total = { let tax = price / 10; // tax only lives inside this block let shipping = 5; price + tax + shipping // no semicolon: this is the block's value };
println!("total = {total}"); // println!("{tax}"); // ❌ error: tax is out of scope}Output:
total = 49Two details to watch:
- The last line
price + tax + shippinghas no semicolon. If you add one, the block gives back()(nothing) instead of a number, andprintln!can’t print it. - The whole thing is a
letstatement, so it ends with};after the closing brace.
Once the block ends, tax and shipping are gone. Only the result, total, is left. Keeping helpers in a small block like this makes it clear they’re only needed for one calculation.
Why scope matters for ownership
Section titled “Why scope matters for ownership”This connects directly to ownership, which you’ll meet a little later: a value is automatically cleaned up the moment its owner’s scope ends.
fn main() { { let text = String::from("hello"); println!("{text}"); } // text's scope ends here — Rust drops it automatically}Output:
helloThat’s the whole mechanism behind Rust needing no garbage collector: it already knows, at compile time, exactly where every scope ends.
Shadowing and scope
Section titled “Shadowing and scope”Remember shadowing from the Variables page? Each let with the same name inside a scope creates a distinct variable — it doesn’t erase the outer one, it just takes over for the rest of that scope:
fn main() { let x = 5; { let x = x * 2; // a new x, only inside this block println!("inner x = {x}"); // 10 } println!("outer x = {x}"); // 5 — unaffected}Output:
inner x = 10outer x = 5Practice
Section titled “Practice”1. Fix the scope error
Section titled “1. Fix the scope error”This program doesn’t compile, because area is used after its block has ended:
fn main() { let width = 3; { let height = 4; let area = width * height; } println!("area = {area}");}Change it so it prints area = 12, while height still lives only inside the block. Hint: make the block produce the area, and store the result in a variable declared outside it.
Show solution
fn main() { let width = 3; let area = { let height = 4; width * height }; println!("area = {area}");}Output:
area = 12area now lives in main’s scope, so println! can see it. height is still only needed inside the block, and it’s dropped when the block ends.
2. Levels of nesting
Section titled “2. Levels of nesting”Create a variable level set to 1. Open a block that shadows it with 2, and inside that, another block that shadows it with 3. Print level in each block, and again right after each block closes. Hint: you should see the numbers go 1, 2, 3, then back down to 2 and 1.
Show solution
fn main() { let level = 1; println!("level {level}"); { let level = 2; println!("level {level}"); { let level = 3; println!("level {level}"); } println!("back to level {level}"); } println!("back to level {level}");}Output:
level 1level 2level 3back to level 2back to level 1Each inner level hides the outer one only until its block ends. The outer variables were never changed, so they’re still there, waiting.
- Scope is the region (marked by
{ }) where a variable is valid. - A variable stops existing the instant its scope’s closing
}is reached. - Function bodies are scopes too — locals inside a function disappear when it returns.
- A block is an expression: its last line (no semicolon) is its value, and its helper variables vanish when it ends.
- Scope is what tells Rust exactly when to drop a value, which is why no garbage collector is needed.
Quick check
Section titled “Quick check”Quick check
1. Why doesn't Rust need a garbage collector, according to this page?
2. What happens when you shadow a variable with let x = x * 2; inside an inner block?
3. What is the value of total after let total = { let a = 2; a * 10 };?
Score: 0 / 3
Read more
Section titled “Read more”- Variable Scope — The Rust Book: how scope works, explained as the first step towards ownership.
- Scope and Shadowing — Rust by Example: a short runnable example of blocks and shadowing.
- RAII — Rust by Example: what happens to values when their scope ends, for when you’re ready for the next step.
- Block expressions — The Rust Reference: the exact rules for blocks and the values they produce.