Core Concept: Variables & Types β
Updated Mar 2026Overview β
Rust is statically typed with powerful type inference. Variables are immutable by default, shadowing replaces mutation for type changes, and the type system catches errors at compile time.
Immutability by Default β
rust
fn main() {
let x = 5;
// x = 6; // ERROR: cannot assign twice to immutable variable
let mut y = 5;
y = 6; // OK: y is mutable
// Constants β must annotate type, must be compile-time evaluable
const MAX_POINTS: u32 = 100_000;
// Static β global lifetime, fixed memory address
static LANGUAGE: &str = "Rust";
}Shadowing β
Shadowing is NOT mutation β it creates a new variable that happens to have the same name.
rust
fn main() {
let x = 5;
let x = x + 1; // New x shadows old x (now 6)
let x = x * 2; // New x shadows again (now 12)
// Shadowing can change types!
let spaces = " "; // &str
let spaces = spaces.len(); // usize β different type, same name
// mut CANNOT change types
// let mut spaces = " ";
// spaces = spaces.len(); // ERROR: mismatched types
}Scalar Types β
Integers β
rust
fn main() {
// Signed integers
let a: i8 = -128; // -128 to 127
let b: i16 = -32_768;
let c: i32 = 42; // Default integer type
let d: i64 = 1_000_000;
let e: i128 = 170_141_183;
let f: isize = 42; // Pointer-sized (64-bit on 64-bit systems)
// Unsigned integers
let g: u8 = 255;
let h: u16 = 65_535;
let i: u32 = 4_294_967_295;
let j: u64 = 42;
let k: u128 = 340_282_366;
let l: usize = 42; // For array indexing
// Literal forms
let decimal = 98_222;
let hex = 0xff;
let octal = 0o77;
let binary = 0b1111_0000;
let byte = b'A'; // u8 only
// Overflow handling
let x: u8 = 255;
// let y: u8 = x + 1; // Panics in debug, wraps in release
let y = x.wrapping_add(1); // Always wraps: 0
let y = x.checked_add(1); // Returns None on overflow
let y = x.saturating_add(1); // Clamps at max: 255
let (y, overflow) = x.overflowing_add(1); // (0, true)
}Floats β
rust
fn main() {
let x = 2.0; // f64 (default)
let y: f32 = 3.0; // f32
// Floating-point comparison β NEVER use ==
let a = 0.1 + 0.2;
// assert_eq!(a, 0.3); // FAILS! Floating point imprecision
assert!((a - 0.3).abs() < f64::EPSILON); // Correct
// Special values
let inf = f64::INFINITY;
let neg_inf = f64::NEG_INFINITY;
let nan = f64::NAN;
assert!(nan.is_nan());
assert!(nan != nan); // NaN is not equal to itself!
}Boolean and Character β
rust
fn main() {
let t: bool = true;
let f: bool = false;
// char is 4 bytes (Unicode scalar value)
let c: char = 'z';
let emoji: char = 'π¦';
let chinese: char = 'δΈ';
assert_eq!(std::mem::size_of::<char>(), 4);
}Compound Types β
Tuples β
rust
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
// Destructuring
let (x, y, z) = tup;
// Index access
let five_hundred = tup.0;
let six_point_four = tup.1;
// Unit tuple β zero-size type
let unit: () = (); // Functions that return nothing return ()
// Functions can return tuples
fn swap(a: i32, b: i32) -> (i32, i32) {
(b, a)
}
}Arrays β
rust
fn main() {
// Fixed-size, stack-allocated
let arr: [i32; 5] = [1, 2, 3, 4, 5];
let zeros = [0; 10]; // [0, 0, ..., 0] (10 elements)
let first = arr[0];
let slice = &arr[1..3]; // &[i32] slice: [2, 3]
// Runtime bounds checking
// let oob = arr[10]; // Panics at runtime!
let safe = arr.get(10); // Returns None
}Type Inference and Turbofish β
rust
fn main() {
// Rust infers types when possible
let x = 42; // i32 (default)
let y = 3.14; // f64 (default)
let s = "hello"; // &str
// Turbofish syntax for explicit type on generic calls
let parsed = "42".parse::<i32>().unwrap();
let numbers: Vec<i32> = vec![1, 2, 3];
let collected = (0..5).collect::<Vec<_>>();
}Type Aliases β
rust
// Simplify complex types
type Thunk = Box<dyn Fn() + Send + 'static>;
type Result<T> = std::result::Result<T, std::io::Error>;
fn takes_long_type(f: Thunk) { }
fn returns_result() -> Result<()> { Ok(()) }Sizes and Alignment β
rust
use std::mem;
fn main() {
println!("bool: {} bytes", mem::size_of::<bool>()); // 1
println!("char: {} bytes", mem::size_of::<char>()); // 4
println!("i32: {} bytes", mem::size_of::<i32>()); // 4
println!("f64: {} bytes", mem::size_of::<f64>()); // 8
println!("&str: {} bytes", mem::size_of::<&str>()); // 16 (ptr + len)
println!("String: {} bytes", mem::size_of::<String>()); // 24 (ptr + len + cap)
println!("Vec<i32>: {} bytes", mem::size_of::<Vec<i32>>()); // 24
println!("Option<i32>: {} bytes", mem::size_of::<Option<i32>>()); // 8
println!("Option<&i32>: {} bytes", mem::size_of::<Option<&i32>>()); // 8 (niche opt!)
}