Variables and Mutability
In Rust, variables are immutable by default. This is a deliberate design choice
that encourages you to write safer code. To make a variable mutable, you must explicitly
opt in with let mut.
fn main() {
// Immutable by default
let x = 5;
// x = 6; // ERROR: cannot assign twice to immutable variable
// Explicitly mutable
let mut y = 10;
println!("y = {y}");
y = 20; // OK — y is mutable
println!("y = {y}");
// Constants: MUST have type annotation, evaluated at compile time
const MAX_POINTS: u32 = 100_000;
const PI: f64 = 3.141_592_653_589_793;
println!("Max: {MAX_POINTS}, PI: {PI}");
}
Immutable by Default: Why?
When a variable is immutable, the compiler can reason about your code better, other readers know the value won't change, and entire classes of bugs are eliminated. Mutability is the exception, not the rule — you opt into complexity only when needed.
Shadowing
Rust allows you to shadow a variable by declaring a new let binding
with the same name. This is different from mut — shadowing creates a brand-new
variable and can even change the type.
fn main() {
let x = 5;
let x = x + 1; // x is now 6 (shadowed)
let x = x * 2; // x is now 12 (shadowed again)
println!("x = {x}"); // 12
// Shadowing can change the type!
let spaces = " "; // &str
let spaces = spaces.len(); // now usize
println!("spaces = {spaces}");
// This would NOT work with mut:
// let mut spaces = " ";
// spaces = spaces.len(); // ERROR: mismatched types
}
Scalar Types
Rust has four primary scalar types: integers, floating-point numbers, booleans, and characters.
Integer Types
| Length | Signed | Unsigned | Range (Signed) |
|---|---|---|---|
| 8-bit | i8 | u8 | -128 to 127 |
| 16-bit | i16 | u16 | -32,768 to 32,767 |
| 32-bit | i32 | u32 | -2B to 2B |
| 64-bit | i64 | u64 | Very large |
| 128-bit | i128 | u128 | Extremely large |
| arch | isize | usize | Depends on platform (32/64-bit) |
fn main() {
// Integer literals
let decimal = 98_222; // Underscores for readability
let hex = 0xff; // Hexadecimal
let octal = 0o77; // Octal
let binary = 0b1111_0000; // Binary
let byte = b'A'; // Byte (u8 only)
// Explicit type annotations
let x: i32 = 42;
let y: u64 = 1_000_000;
let z: i8 = -128;
// Floating-point types
let f1 = 2.0; // f64 (default)
let f2: f32 = 3.0; // f32
// Boolean
let t: bool = true;
let f = false;
// Character (4 bytes, Unicode scalar value)
let c = 'z';
let heart = '❤';
let emoji = '🦀'; // The Rust mascot: Ferris the crab!
println!("{decimal} {hex} {octal} {binary} {byte}");
println!("{f1} {f2} {t} {f} {c} {heart} {emoji}");
}
Compound Types
fn main() {
// Tuples: fixed-size, mixed types
let tup: (i32, f64, bool) = (500, 6.4, true);
let (x, y, z) = tup; // Destructuring
println!("x={x}, y={y}, z={z}");
println!("First: {}", tup.0); // Index access
// Unit type: empty tuple ()
let _unit: () = ();
// Arrays: fixed-size, same type, stack-allocated
let arr = [1, 2, 3, 4, 5];
let first = arr[0];
let second = arr[1];
println!("first={first}, second={second}");
// Array with repeated value
let zeros = [0; 5]; // [0, 0, 0, 0, 0]
// Type-annotated array
let months: [&str; 4] = ["Jan", "Feb", "Mar", "Apr"];
// Rust checks array bounds at runtime!
// let crash = arr[10]; // panic: index out of bounds
println!("{:?} {:?}", zeros, months);
}
Type Inference & Annotations
fn main() {
// Rust infers types from context
let x = 42; // inferred as i32
let y = 3.14; // inferred as f64
let active = true; // inferred as bool
let name = "Rust"; // inferred as &str
// Sometimes you MUST annotate
let guess: u32 = "42".parse().expect("Not a number!");
// Numeric operations
let sum = 5 + 10;
let difference = 95.5 - 4.3;
let product = 4 * 30;
let quotient = 56.7 / 32.2;
let remainder = 43 % 5;
// Type casting with 'as'
let x: i32 = 42;
let y: f64 = x as f64;
let z: u8 = 255;
println!("{sum} {difference} {product} {quotient} {remainder}");
println!("{y} {z}");
}
Key Takeaways
- ✅ Variables are immutable by default — use
let mutfor mutability - ✅ Shadowing creates new variables and can change types
- ✅ Rust has rich scalar types: integers (i8-i128, u8-u128), floats, bool, char
- ✅ Compound types include tuples (mixed types) and arrays (same type, fixed size)
- ✅ Type inference is powerful, but annotations are needed for ambiguous cases