Chapter 3 — Common Programming Concepts
Fundamental Rust concepts: variables, data types, functions, and control flow.
Variables and Mutability
#![allow(unused)] fn main() { let x = 5; // immutable let mut y = 10; // mutable y = 15; // OK const MAX_POINTS: u32 = 100_000; // constant (always immutable) }
Shadowing:
#![allow(unused)] fn main() { let x = 5; let x = x + 1; // shadows previous x let x = x * 2; // shadows again }
Shadowing lets you reuse names and change types:
#![allow(unused)] fn main() { let spaces = " "; let spaces = spaces.len(); // different type, same name }
Data Types
Scalar Types
Integers:
#![allow(unused)] fn main() { let a: i32 = -42; // signed 32-bit let b: u64 = 100; // unsigned 64-bit }
Types: i8, i16, i32, i64, i128, isize (signed)
Types: u8, u16, u32, u64, u128, usize (unsigned)
Floats:
#![allow(unused)] fn main() { let x = 2.0; // f64 (default) let y: f32 = 3.0; // f32 }
Booleans:
#![allow(unused)] fn main() { let t = true; let f: bool = false; }
Characters:
#![allow(unused)] fn main() { let c = 'z'; let emoji = '😻'; // Unicode scalar value }
Compound Types
Tuples:
#![allow(unused)] fn main() { let tup: (i32, f64, u8) = (500, 6.4, 1); let (x, y, z) = tup; // destructuring let five_hundred = tup.0; // index access }
Arrays:
#![allow(unused)] fn main() { let a = [1, 2, 3, 4, 5]; let months = ["Jan", "Feb", "Mar"]; let a: [i32; 5] = [1, 2, 3, 4, 5]; // type annotation let a = [3; 5]; // [3, 3, 3, 3, 3] let first = a[0]; }
Functions
fn main() { another_function(5, 'h'); } fn another_function(x: i32, unit_label: char) { println!("Value: {x}{unit_label}"); }
Return values:
#![allow(unused)] fn main() { fn five() -> i32 { 5 // no semicolon = expression (returns value) } fn plus_one(x: i32) -> i32 { x + 1 // expression } }
Statements vs Expressions:
- Statements don't return values:
let y = 6; - Expressions return values:
5 + 6,{ let x = 3; x + 1 }
#![allow(unused)] fn main() { let y = { let x = 3; x + 1 // no semicolon = expression }; // y = 4 }
Control Flow
if/else
#![allow(unused)] fn main() { let number = 6; if number % 4 == 0 { println!("divisible by 4"); } else if number % 3 == 0 { println!("divisible by 3"); } else { println!("not divisible by 4 or 3"); } }
if in let:
#![allow(unused)] fn main() { let condition = true; let number = if condition { 5 } else { 6 }; }
Loops
loop (infinite):
#![allow(unused)] fn main() { loop { println!("again!"); break; // exit loop } }
Return from loop:
#![allow(unused)] fn main() { let result = loop { counter += 1; if counter == 10 { break counter * 2; } }; }
while:
#![allow(unused)] fn main() { let mut number = 3; while number != 0 { println!("{number}!"); number -= 1; } }
for:
#![allow(unused)] fn main() { let a = [10, 20, 30, 40, 50]; for element in a { println!("{element}"); } for number in (1..4).rev() { // 3, 2, 1 println!("{number}!"); } }
Quick Reference
- Variables immutable by default, use
mutfor mutability - Constants use
constand must have type annotation - Shadowing allows reusing variable names
- Rust is statically typed (compiler infers types)
- Tuples have fixed size, mixed types
- Arrays have fixed size, same type
- Functions must declare parameter types
- Last expression in function is return value (no semicolon)
ifis an expression (can return values)forloops are safest for iteration