Variables and Mutability
Variables and Mutability in Rust
Rust's approach to variables is unique - variables are immutable by default. This design choice helps prevent bugs and makes code easier to reason about.
Immutable Variables
By default, variables in Rust are immutable:
fn main() {
let x = 5;
println!("The value of x is: {}", x);
// This will cause a compile error:
// x = 6; // Error: cannot assign twice to immutable variable
}
Mutable Variables
To make a variable mutable, use the mut
keyword:
fn main() {
let mut x = 5;
println!("The value of x is: {}", x);
x = 6; // This is allowed
println!("The value of x is now: {}", x);
}
Constants
Constants are always immutable and must have a type annotation:
const MAX_POINTS: u32 = 100_000;
const PI: f64 = 3.14159;
fn main() {
println!("Max points: {}", MAX_POINTS);
println!("Pi value: {}", PI);
}
Key differences between constants and immutable variables:
- Constants use
const
keyword, notlet
- Type annotation is required for constants
- Constants can be declared in any scope
- Constants must be set to a constant expression
Shadowing
Rust allows you to declare a new variable with the same name as a previous variable:
fn main() {
let x = 5;
let x = x + 1; // Shadows the previous x
{
let x = x * 2; // Shadows in inner scope
println!("Inner scope x: {}", x); // Prints: 12
}
println!("Outer scope x: {}", x); // Prints: 6
}
Shadowing allows changing the type:
fn main() {
let spaces = " ";
let spaces = spaces.len(); // Now it's a number
println!("Number of spaces: {}", spaces);
}
Variable Naming Conventions
Rust follows these naming conventions:
- Variables:
snake_case
- Constants:
SCREAMING_SNAKE_CASE
- Types:
CamelCase
const MAX_SIZE: usize = 100;
fn main() {
let user_name = "Alice";
let age_in_years = 30;
let is_active = true;
}
Type Annotations
Rust can usually infer types, but sometimes you need to specify:
fn main() {
// Type inference
let x = 5; // Rust infers i32
// Explicit type annotation
let y: i64 = 5;
// Parsing requires type annotation
let guess: u32 = "42".parse().expect("Not a number!");
}
Multiple Variable Declaration
You can declare multiple variables at once using patterns:
fn main() {
// Tuple destructuring
let (x, y, z) = (1, 2, 3);
// With type annotations
let (a, b): (i32, f64) = (10, 3.14);
println!("x={}, y={}, z={}", x, y, z);
println!("a={}, b={}", a, b);
}
Unused Variables
Rust warns about unused variables. Prefix with underscore to suppress warning:
fn main() {
let _unused_variable = 42; // No warning
let used_variable = 10;
println!("Used: {}", used_variable);
}
Best Practices
- Prefer immutability: Only use
mut
when necessary - Use descriptive names:
user_count
instead ofuc
- Use constants for magic numbers: Define constants instead of hardcoding values
- Shadow wisely: Use shadowing for transformations, not confusion
Examples
Temperature Converter
fn main() {
let celsius = 25.0;
let fahrenheit = celsius * 9.0/5.0 + 32.0;
println!("{}°C = {}°F", celsius, fahrenheit);
}
Using Mutability
fn main() {
let mut counter = 0;
println!("Initial counter: {}", counter);
counter += 1;
println!("After increment: {}", counter);
counter *= 2;
println!("After doubling: {}", counter);
}
Constants in Practice
const SPEED_OF_LIGHT: u64 = 299_792_458; // m/s
const SECONDS_IN_HOUR: u32 = 3_600;
fn main() {
let hours = 2;
let distance = SPEED_OF_LIGHT * (hours * SECONDS_IN_HOUR) as u64;
println!("Light travels {} meters in {} hours", distance, hours);
}