Introduction to Scope in Rust
Scope defines the region of a program where a particular binding (variable, function, type, etc.) is valid and accessible. Rust's scope rules are fundamental to its ownership model, memory safety guarantees, and resource management. Understanding scope is crucial for writing correct and efficient Rust code.
Key Concepts
- Block Scope: Variables are valid only within their declaring block
{} - Ownership: Each value has a single owner within its scope
- Lifetime: The period during which a reference is valid
- Shadowing: Reusing variable names within nested scopes
- Visibility: Module-level scope and access control (
pubvs private) - Temporal Scope: When resources are acquired and released (RAII)
1. Basic Block Scope
Simple Block Scope
fn main() {
// Outer scope
let outer_var = 42;
println!("Outer: {}", outer_var);
{
// Inner scope
let inner_var = 100;
println!("Inner: {}", inner_var);
println!("Access outer from inner: {}", outer_var); // Can access outer
} // inner_var goes out of scope here
// println!("{}", inner_var); // Error: inner_var not found in this scope
// Scope with multiple blocks
let x = 5;
let y = {
let x = 10; // Different x, shadows outer
x + 5 // Returns 15
}; // Inner x goes out of scope
println!("x: {}, y: {}", x, y); // x: 5, y: 15
// Nested blocks for organization
let result = {
let temp = 42;
let processed = temp * 2;
processed + 10
};
println!("Result: {}", result); // 94
}
Function Scope
fn main() {
let main_var = "Hello from main";
println!("{}", main_var);
// Call function with its own scope
function_scope();
// Variables are passed between scopes
let value = 42;
let result = process_value(value);
println!("Processed: {}", result);
// value still accessible (i32 is Copy)
println!("Original still available: {}", value);
}
fn function_scope() {
// This function has its own scope
let func_var = "Inside function";
println!("{}", func_var);
// Can't access main_var from here
// println!("{}", main_var); // Error: main_var not in scope
}
fn process_value(x: i32) -> i32 {
// x is in scope here
let doubled = x * 2;
doubled // Return value moves to caller's scope
} // x and doubled go out of scope here
2. Ownership and Scope
Ownership Rules in Scope
#[derive(Debug)]
struct Resource {
name: String,
data: Vec<i32>,
}
impl Drop for Resource {
fn drop(&mut self) {
println!("Resource '{}' is being dropped", self.name);
}
}
fn main() {
// Ownership scope demonstration
let resource = Resource {
name: String::from("main_resource"),
data: vec![1, 2, 3],
};
println!("Created: {:?}", resource);
{
// Move ownership to inner scope
let moved = resource; // resource moves here
println!("Moved to inner: {:?}", moved);
// moved dropped at end of this scope
} // Resource 'main_resource' dropped here
// println!("{:?}", resource); // Error: resource moved
// Clone to keep original
let resource2 = Resource {
name: String::from("clone_source"),
data: vec![4, 5, 6],
};
let cloned = Resource {
name: resource2.name.clone(),
data: resource2.data.clone(),
};
println!("Original: {:?}", resource2);
println!("Clone: {:?}", cloned);
// Both stay in scope
// Borrowing across scopes
let data = vec![1, 2, 3];
let borrowed = &data; // Borrow
println!("Borrowed: {:?}", borrowed);
{
let borrowed_again = &data; // Another borrow
println!("Also borrowed: {:?}", borrowed_again);
} // borrowed_again out of scope
// Can still use borrowed
println!("Still borrowed: {:?}", borrowed);
} // All remaining resources dropped here
RAII (Resource Acquisition Is Initialization)
use std::fs::File;
use std::io::Write;
struct DatabaseConnection {
id: u32,
connected: bool,
}
impl DatabaseConnection {
fn new(id: u32) -> Self {
println!("Opening database connection {}", id);
DatabaseConnection { id, connected: true }
}
fn query(&self, sql: &str) {
if self.connected {
println!("Connection {}: Executing: {}", self.id, sql);
}
}
}
impl Drop for DatabaseConnection {
fn drop(&mut self) {
println!("Closing database connection {}", self.id);
self.connected = false;
}
}
struct FileHandler {
file: File,
path: String,
}
impl FileHandler {
fn new(path: &str) -> std::io::Result<Self> {
let file = File::create(path)?;
Ok(FileHandler {
file,
path: path.to_string(),
})
}
fn write(&mut self, content: &str) -> std::io::Result<()> {
self.file.write_all(content.as_bytes())
}
}
impl Drop for FileHandler {
fn drop(&mut self) {
println!("Closing file: {}", self.path);
// File automatically closed when dropped
}
}
fn main() {
// Resources automatically cleaned up when they go out of scope
{
let conn = DatabaseConnection::new(1);
conn.query("SELECT * FROM users");
} // conn dropped here
println!("Connection closed, continuing...");
// Multiple resources in nested scopes
{
let conn1 = DatabaseConnection::new(2);
{
let conn2 = DatabaseConnection::new(3);
conn1.query("SELECT FROM orders");
conn2.query("SELECT FROM products");
} // conn2 dropped here
conn1.query("SELECT FROM customers");
} // conn1 dropped here
// File handling with RAII
{
let mut file = FileHandler::new("test.txt").unwrap();
file.write("Hello, World!").unwrap();
println!("Data written to file");
} // File automatically closed and flushed here
}
3. Shadowing
Basic Shadowing
fn main() {
// Shadowing with same type
let x = 5;
println!("x: {}", x); // 5
let x = x + 1; // Shadows previous x
println!("x: {}", x); // 6
let x = x * 2; // Shadows again
println!("x: {}", x); // 12
// Shadowing with different type
let name = "Alice";
println!("name: {}", name); // "Alice"
let name = name.len(); // Now name is usize
println!("name length: {}", name); // 5
// Shadowing in nested scopes
let y = 10;
println!("outer y: {}", y); // 10
{
let y = 20; // Shadows outer y
println!("inner y: {}", y); // 20
{
let y = 30; // Shadows again
println!("deep inner y: {}", y); // 30
}
println!("back to inner y: {}", y); // 20
}
println!("back to outer y: {}", y); // 10
// Shadowing with mutability change
let value = 5;
// value += 1; // Error: immutable
let mut value = value; // Now mutable
value += 5;
println!("value: {}", value); // 10
}
Advanced Shadowing Patterns
fn main() {
// Shadowing for type conversion
let input = "42";
let input: i32 = input.parse().unwrap();
let input = input * 2;
println!("input: {}", input); // 84
// Shadowing in loops
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
// Shadowing with Option
let maybe_value: Option<i32> = Some(10);
if let Some(value) = maybe_value {
// value shadows outer scope
println!("Got value: {}", value);
}
// Shadowing in match expressions
let x = Some(5);
match x {
Some(x) => println!("Matched: {}", x), // x shadows outer x
None => (),
}
// Shadowing for transformation pipelines
let data = vec![1, 2, 3, 4, 5];
let data = data.iter()
.map(|&x| x * 2)
.collect::<Vec<_>>();
let data = data.iter()
.filter(|&&x| x > 5)
.collect::<Vec<_>>();
println!("Transformed: {:?}", data);
// Shadowing for validation
fn process_input(input: &str) -> Result<i32, &str> {
let input = input.trim();
if input.is_empty() {
return Err("Empty input");
}
let input = input.parse::<i32>().map_err(|_| "Invalid number")?;
if input < 0 {
return Err("Number must be positive");
}
Ok(input)
}
match process_input(" 42 ") {
Ok(value) => println!("Valid input: {}", value),
Err(e) => println!("Error: {}", e),
}
}
4. Lifetime Scope
Basic Lifetimes
fn main() {
// Lifetime scope examples
let r; // ---------+-- 'a
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// println!("{}", r); // Error: x dropped, reference invalid
// Valid reference
let x = 5; // ---------+-- 'b
let r = &x; // |
println!("{}", r); // |
// Valid, x lives long enough
// Function with lifetime parameters
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
println!("Longest: {}", result);
} // string2 dropped, result still valid if it references string1
// But this would be invalid:
// let result;
// {
// let string2 = String::from("xyz");
// result = longest(string1.as_str(), string2.as_str());
// }
// println!("{}", result); // Error if result refers to string2
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
Lifetime Elision and Rules
// No explicit lifetimes needed (elision rules apply)
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
// Multiple parameters with lifetimes
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
// Lifetime elision applies here
fn level(&self) -> i32 {
3
}
// Return lifetime is self's lifetime
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention please: {}", announcement);
self.part
}
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().unwrap();
let excerpt = ImportantExcerpt {
part: first_sentence,
};
// excerpt's lifetime tied to novel's scope
println!("Excerpt: {}", excerpt.part);
// Static lifetime
let static_str: &'static str = "I live forever";
println!("Static: {}", static_str);
}
5. Module Scope and Visibility
Module-Level Scope
// lib.rs or main.rs
// Public module
pub mod outer {
// Public items
pub fn public_function() {
println!("Called outer::public_function");
private_function(); // Can call private within same module
inner::public_inner(); // Can call public inner
}
// Private by default
fn private_function() {
println!("Called outer::private_function");
}
// Public nested module
pub mod inner {
pub fn public_inner() {
println!("Called outer::inner::public_inner");
private_inner();
}
fn private_inner() {
println!("Called outer::inner::private_inner");
}
// Nested deeper
pub mod deeper {
pub fn deepest() {
println!("Called outer::inner::deeper::deepest");
}
}
}
// Struct with public and private fields
pub struct Person {
pub name: String, // Public field
age: u32, // Private field
}
impl Person {
pub fn new(name: String, age: u32) -> Self {
Person { name, age }
}
pub fn get_age(&self) -> u32 {
self.age
}
// Private method
fn validate(&self) -> bool {
self.age > 0
}
}
}
fn main() {
// Accessing public items
outer::public_function();
outer::inner::public_inner();
outer::inner::deeper::deepest();
// Creating struct with public constructor
let person = outer::Person::new("Alice".to_string(), 30);
println!("Name: {}", person.name); // Public field accessible
// println!("Age: {}", person.age); // Error: private field
// Using public method
println!("Age: {}", person.get_age());
// Can't access private functions
// outer::private_function(); // Error
// outer::inner::private_inner(); // Error
}
Use Declarations and Scope
// Bring items into scope with 'use'
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
fn seat_at_table() {}
}
pub mod serving {
pub fn take_order() {}
pub fn serve_order() {}
fn take_payment() {}
}
}
// Bring into scope
use front_of_house::hosting;
use front_of_house::serving::{self, take_order};
// Rename with 'as'
use std::fmt::Result as FmtResult;
use std::io::Result as IoResult;
// Re-export
pub use front_of_house::hosting::add_to_waitlist;
fn main() {
// Can use without full path
hosting::add_to_waitlist();
take_order();
serving::serve_order();
// Nested paths
use std::collections::{HashMap, HashSet, VecDeque};
let mut map = HashMap::new();
let set = HashSet::new();
let deque = VecDeque::new();
// Glob imports (use sparingly)
use std::fmt::*;
// Format! is now in scope without std::fmt::
let s = format!("Hello");
}
// Use in specific scope
fn function() {
// Limited to this function
use std::mem::size_of;
println!("Size of i32: {} bytes", size_of::<i32>());
}
mod nested {
pub fn test() {
// Different scope, need separate use
use super::front_of_house::hosting;
hosting::add_to_waitlist();
}
}
6. Temporal Scope and Drop Order
Drop Order
struct Important {
name: String,
level: i32,
}
impl Drop for Important {
fn drop(&mut self) {
println!("Dropping Important '{}' (level {})", self.name, self.level);
}
}
struct Container {
name: String,
items: Vec<Important>,
}
impl Drop for Container {
fn drop(&mut self) {
println!("Dropping Container '{}'", self.name);
// Items dropped after container (fields dropped in declaration order)
}
}
fn main() {
// Variables dropped in reverse declaration order
let a = Important { name: "A".to_string(), level: 1 };
let b = Important { name: "B".to_string(), level: 2 };
let c = Important { name: "C".to_string(), level: 3 };
println!("End of scope - variables will be dropped in reverse order: C, B, A");
// Output order: C, B, A
// Nested structures
let container = Container {
name: "Main".to_string(),
items: vec![
Important { name: "Item1".to_string(), level: 1 },
Important { name: "Item2".to_string(), level: 2 },
],
};
println!("Container and items will be dropped soon");
// Drop order: items (Item2, Item1) then container
}
RAII and Scope-Based Resource Management
use std::sync::Mutex;
struct LockGuard<'a> {
mutex: &'a Mutex<i32>,
data: i32,
}
impl<'a> LockGuard<'a> {
fn new(mutex: &'a Mutex<i32>) -> Self {
println!("Acquiring lock");
let data = *mutex.lock().unwrap();
LockGuard { mutex, data }
}
fn update(&mut self, new_value: i32) {
self.data = new_value;
}
}
impl<'a> Drop for LockGuard<'a> {
fn drop(&mut self) {
println!("Releasing lock with value: {}", self.data);
*self.mutex.lock().unwrap() = self.data;
}
}
struct Transaction {
id: u32,
active: bool,
}
impl Transaction {
fn begin(id: u32) -> Self {
println!("Transaction {}: BEGIN", id);
Transaction { id, active: true }
}
fn commit(mut self) {
if self.active {
println!("Transaction {}: COMMIT", self.id);
self.active = false;
}
}
fn rollback(mut self) {
if self.active {
println!("Transaction {}: ROLLBACK", self.id);
self.active = false;
}
}
}
impl Drop for Transaction {
fn drop(&mut self) {
if self.active {
println!("Transaction {}: AUTO-ROLLBACK on drop", self.id);
}
}
}
fn main() {
let mutex = Mutex::new(0);
{
let mut guard = LockGuard::new(&mutex);
println!("Current value: {}", guard.data);
guard.update(42);
println!("Updated to: {}", guard.data);
} // Lock released here
println!("Final value: {}", *mutex.lock().unwrap()); // 42
// Transaction with automatic rollback
{
let tx = Transaction::begin(1);
// Do some work
// If commit not called, auto-rollback on drop
} // Auto-rollback
// Transaction with explicit commit
{
let tx = Transaction::begin(2);
// Do work
tx.commit(); // Explicit commit
} // No rollback, already committed
}
7. Loop and Closure Scopes
Loop Scope
fn main() {
// Variables in loop scope
let mut counter = 0;
loop {
let iteration = counter; // Created each iteration
println!("Iteration: {}", iteration);
counter += 1;
if counter >= 3 {
break;
}
} // iteration dropped each loop end
// For loop scope
for i in 0..3 {
let squared = i * i; // Created each iteration
println!("{} squared = {}", i, squared);
} // squared dropped each iteration
// While let scope
let mut numbers = vec![1, 2, 3];
while let Some(num) = numbers.pop() {
let doubled = num * 2; // Created each iteration
println!("{} doubled = {}", num, doubled);
} // doubled dropped each iteration
// Variables persist across iterations
let mut sum = 0;
for i in 1..=5 {
sum += i; // sum persists, modified each iteration
println!("After adding {}: sum = {}", i, sum);
}
println!("Final sum: {}", sum);
}
Closure Scope
fn main() {
// Closures capture variables from their scope
let x = 42;
let print_x = || {
println!("x = {}", x); // Captures x by reference
};
print_x(); // Works
println!("Still have x: {}", x); // x still available
// Moving into closure
let data = vec![1, 2, 3];
let consume_data = move || {
println!("Data: {:?}", data); // data moved here
};
consume_data();
// println!("{:?}", data); // Error: data moved
// Closure returning captured value
let y = 10;
let get_y = || y; // Captures by copy (i32 is Copy)
let value = get_y();
println!("y: {}, value: {}", y, value); // Both available
// Different capture modes
let mut counter = 0;
let mut increment = || {
counter += 1; // Mutable borrow
counter
};
println!("Counter: {}", increment()); // 1
println!("Counter: {}", increment()); // 2
// Can't use counter while mutably borrowed by closure
// println!("{}", counter); // Error
println!("Final counter: {}", increment()); // 3
// After closure used, can access counter again
println!("Counter directly: {}", counter); // 3
}
8. Generic Scope and Trait Bounds
Generic Parameter Scope
use std::fmt::Display;
// Generic parameters scoped to the function/struct
fn print_pair<T: Display, U: Display>(first: T, second: U) {
println!("First: {}, Second: {}", first, second);
} // T and U go out of scope after function
struct Container<T> {
// T in scope for entire struct
item: T,
count: u32,
}
impl<T> Container<T> {
// T still in scope for impl block
fn new(item: T) -> Self {
Container { item, count: 0 }
}
fn get(&self) -> &T {
&self.item
}
} // T out of scope after impl
// Multiple generic parameters
struct Pair<A, B> {
first: A,
second: B,
}
impl<A, B> Pair<A, B> {
fn new(first: A, second: B) -> Self {
Pair { first, second }
}
// Method with additional generic parameter
fn convert<C, D, F, G>(self, f: F, g: G) -> Pair<C, D>
where
F: FnOnce(A) -> C,
G: FnOnce(B) -> D,
{
Pair {
first: f(self.first),
second: g(self.second),
}
}
}
fn main() {
let pair = Pair::new(42, "hello");
let converted = pair.convert(
|n| n.to_string(),
|s| s.len(),
);
// converted: Pair<String, usize>
}
Trait Bound Scope
use std::fmt::Debug;
// Trait bounds create scope for associated types
trait Container {
type Item;
fn get(&self) -> &Self::Item;
fn set(&mut self, item: Self::Item);
}
// Where clause for complex bounds
fn process_items<T, U>(items: Vec<T>, mut f: U) -> Vec<T::Item>
where
T: Container,
U: FnMut(&T::Item) -> bool,
{
let mut result = Vec::new();
for item in items {
if f(item.get()) {
// T::Item in scope here
// Can't create T::Item directly, need to get from container
}
}
result
}
// Higher-ranked trait bounds
fn call_on_ref<F>(f: F)
where
F: for<'a> Fn(&'a i32) -> &'a i32, // HRTB
{
let x = 42;
let y = f(&x);
println!("Result: {}", y);
}
fn main() {
call_on_ref(|x| x); // Works with any lifetime
// Multiple bounds
fn clone_and_print<T: Clone + Debug>(value: &T) {
let cloned = value.clone();
println!("{:?}", cloned);
}
let x = 42;
clone_and_print(&x);
}
9. Pattern Matching Scope
Match Expression Scope
fn main() {
// Match arms have their own scope
let value = Some(42);
let result = match value {
Some(x) => {
// x in scope here
let doubled = x * 2;
doubled.to_string()
}
None => {
// Different scope
String::from("none")
}
};
// x not accessible here
// Match guards with scope
let pair = (10, 20);
match pair {
(x, y) if x > y => println!("First larger: {}", x),
(x, y) if x < y => println!("Second larger: {}", y),
(x, y) => println!("Equal: {}", x),
}
// Binding with @
let id = 5;
match id {
e @ 1..=5 => println!("Small: {}", e),
e @ 6..=10 => println!("Medium: {}", e),
e => println!("Large: {}", e),
}
// Ref patterns
let data = Some(String::from("hello"));
match data {
Some(ref s) => println!("Borrowed: {}", s), // s is &String
None => (),
}
println!("Still have: {:?}", data); // data not moved
// Nested patterns
struct Point { x: i32, y: i32 }
let point = Point { x: 10, y: 20 };
match point {
Point { x, y } => {
println!("Point at ({}, {})", x, y);
}
}
}
If Let and While Let Scope
fn main() {
// if let creates scope for the bound variable
let optional = Some(42);
if let Some(x) = optional {
// x in scope here
println!("Value: {}", x);
} // x goes out of scope
// x not accessible here
// Multiple patterns
let result: Result<i32, &str> = Ok(42);
if let Ok(x) = result {
println!("Success: {}", x);
} else if let Err(e) = result {
println!("Error: {}", e);
}
// while let creates scope each iteration
let mut numbers = vec![1, 2, 3];
while let Some(x) = numbers.pop() {
// x in scope for this iteration
println!("Popped: {}", x);
// x dropped at end of iteration
}
// Combined with conditions
let mut data = vec![Some(1), Some(2), None, Some(3)];
while let Some(Some(x)) = data.pop() {
// Only matches Some containing a value
println!("Got: {}", x);
}
}
10. Macro Scope
Macro Hygiene and Scope
// Macros have hygienic scope (they don't capture variables from calling scope)
macro_rules! create_var {
($name:ident, $value:expr) => {
let $name = $value;
println!("Created variable: {}", $name);
};
}
macro_rules! print_sum {
($a:expr, $b:expr) => {
println!("Sum of {} and {} is {}", $a, $b, $a + $b);
};
}
macro_rules! with_scope {
($x:expr, $body:block) => {
{
let x = $x;
println!("Inside macro: x = {}", x);
$body
println!("After body: x = {}", x);
}
};
}
macro_rules! capture_ident {
($name:ident) => {
let $name = 42; // Creates new variable
println!("Inside macro: {} = {}", stringify!($name), $name);
};
}
fn main() {
// Macros create their own scope
create_var!(my_var, 100);
println!("Outside: my_var = {}", my_var);
// Variables from outside scope
let x = 10;
let y = 20;
print_sum!(x, y); // Works, but x and y are not captured, just passed as expressions
// Macro with block
with_scope!(5, {
println!("Inside block: x is not accessible here");
// Can't access x from outer scope here
});
// Macro hygiene - doesn't accidentally capture
let value = 10;
capture_ident!(value); // Creates its own 'value' variable
println!("Outside: value = {}", value); // Still 10, not affected
// Macro with local variables
let mut count = 0;
macro_rules! increment {
() => {
count += 1; // Captures count from scope
println!("Count: {}", count);
};
}
increment!();
increment!();
println!("Final count: {}", count); // 2
}
11. Concurrency Scope
Thread Scope
use std::thread;
use std::sync::{Arc, Mutex};
fn main() {
// Variables moved into thread
let data = vec![1, 2, 3];
let handle = thread::spawn(move || {
// data moved here
println!("Thread data: {:?}", data);
});
handle.join().unwrap();
// println!("{:?}", data); // Error: data moved
// Shared ownership with Arc
let shared = Arc::new(vec![4, 5, 6]);
let mut handles = vec![];
for i in 0..3 {
let shared = Arc::clone(&shared);
handles.push(thread::spawn(move || {
println!("Thread {} sees: {:?}", i, shared);
}));
}
for handle in handles {
handle.join().unwrap();
}
println!("Main still has: {:?}", shared);
// Mutex for mutable shared data
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
handles.push(thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
}));
}
for handle in handles {
handle.join().unwrap();
}
println!("Final count: {}", *counter.lock().unwrap());
// Scoped threads (crossbeam or scope API)
let mut data = vec![1, 2, 3];
thread::scope(|s| {
s.spawn(|| {
data.push(4);
});
s.spawn(|| {
data.push(5);
});
});
println!("Data after scope: {:?}", data);
}
Channel Scope
use std::sync::mpsc;
use std::thread;
fn main() {
// Channels cross thread boundaries
let (tx, rx) = mpsc::channel();
let handle = thread::spawn(move || {
let values = vec![1, 2, 3, 4, 5];
for value in values {
tx.send(value).unwrap();
thread::sleep(std::time::Duration::from_millis(100));
}
});
// Receiver scope
for received in rx {
println!("Got: {}", received);
}
handle.join().unwrap();
// Multiple producers
let (tx, rx) = mpsc::channel();
let tx1 = tx.clone();
let tx2 = tx.clone();
thread::spawn(move || {
tx1.send("from thread 1").unwrap();
});
thread::spawn(move || {
tx2.send("from thread 2").unwrap();
});
for _ in 0..2 {
println!("Received: {}", rx.recv().unwrap());
}
}
Conclusion
Rust's scope rules are fundamental to its safety guarantees:
Key Takeaways
- Block Scope: Variables live only within their declaring block
- Ownership Scope: Each value has exactly one owner
- Lifetime Scope: References must not outlive their data
- Shadowing: Reuse names in nested scopes
- Module Scope: Visibility controlled with
pub - RAII: Resources released when scope ends
- Closure Scope: Capture variables from environment
- Thread Scope: Variables must be safe to send between threads
Scope Rules Summary
- Variables are valid from declaration to end of block
- Values are dropped when they go out of scope (RAII)
- References must be valid for entire borrowed period
- Shadowing creates new variable, doesn't modify old
- Modules control visibility with
pubkeyword - Closures capture variables from creating scope
- Threads require ownership or synchronization
Best Practices
- Minimize scope of variables for clarity
- Use blocks to limit temporary variables
- Leverage RAII for automatic cleanup
- Understand drop order for complex structures
- Use shadowing judiciously for transformations
- Control visibility with module scope
- Consider lifetimes when designing APIs
Rust's scope rules, combined with ownership and borrowing, provide compile-time memory safety without garbage collection, making it both powerful and efficient.