PHASE 1 ← Back to Course
2 / 17
📖

Rust Fundamentals

Variables, data types, functions, and control flow, the building blocks of every Rust program.

1

Variables and Mutability

In Rust, variables are immutable by default. This is a key safety feature that prevents accidental modifications and makes code easier to reason about. To make a variable mutable, you explicitly use the mut keyword.

Immutable Variables

Rust
fn main() {
    let x = 5;
    println!("x = {}", x);
    // This will cause a compile error:
    // x = 6;
}

Mutable Variables

When you need to change a variable's value, explicitly declare it as mutable:

Rust
fn main() {
    let mut x = 5;
    println!("x = {}", x);
    x = 6;
    println!("x = {}", x);
}

Constants

Constants are always immutable and must be annotated with their type. They can be declared at module scope:

Rust
const MAX_POINTS: u32 = 100_000;
const PI: f64 = 3.14159;
fn main() {
    println!("Maximum points: {}", MAX_POINTS);
    println!("Pi: {}", PI);
}
💡

Constants vs Variables

Constants cannot be shadowed, and they're evaluated at compile-time. Variables can be shadowed (redeclared with the same name), which allows changing a value and potentially its type.

Shadowing

Shadowing allows you to reuse variable names and even change types:

Rust
fn main() {
    let x = 5;
    let x = x + 1;  // x is now 6
    let x = x * 2;  // x is now 12
    // You can even change the type:
    let spaces = "   ";
    let spaces = spaces.len();  // spaces is now a number (3)
    println!("x = {}, spaces = {}", x, spaces);
}
2

Data Types

Rust is a statically typed language, meaning the compiler must know the type of every variable at compile-time. Rust can often infer types, but you can also annotate them explicitly.

Scalar Types

Scalar types represent single values. Rust has four primary scalar types.

Integer Types

Signed and unsigned integers of various sizes:

Rust
fn main() {
    // Signed integers (can be negative)
    let a: i32 = -42;     // 32-bit signed
    let b: i64 = -1000;   // 64-bit signed
    let c: i8 = -5;       // 8-bit signed (range -128 to 127)
    // Unsigned integers (always positive)
    let d: u32 = 42;      // 32-bit unsigned
    let e: u64 = 1000;    // 64-bit unsigned
    let f: u8 = 255;      // 8-bit unsigned (range 0 to 255)
    // Without annotation, integers default to i32
    let g = 100;
    println!("a={}, b={}, c={}, d={}, e={}, f={}, g={}", a, b, c, d, e, f, g);
}

Floating-Point Types

IEEE 754 standard floating-point numbers:

Rust
fn main() {
    let x: f64 = 3.14159;    // 64-bit float (default)
    let y: f32 = 2.71828;    // 32-bit float
    // Operations on floats
    let sum = x + y as f64;
    let product = x * y as f64;
    println!("sum = {}, product = {}", sum, product);
}

Boolean Type

Logical true/false values:

Rust
fn main() {
    let is_active: bool = true;
    let is_ready = false;  // Type inferred as bool
    // Booleans are used in conditionals
    if is_active && !is_ready {
        println!("Active but not ready");
    }
}

Character Type

Unicode scalar values:

Rust
fn main() {
    let c: char = 'z';
    let emoji: char = '🦀';
    let greek: char = 'π';
    println!("char: {}, emoji: {}, greek: {}", c, emoji, greek);
}

Compound Types

Compound types group multiple values into one type. The two primitive compound types are tuples and arrays.

Tuples

Fixed-length collections of values with potentially different types:

Rust
fn main() {
    // Tuple with mixed types
    let tup: (i32, f64, u8) = (500, 6.4, 1);
    // Access tuple elements with destructuring
    let (x, y, z) = tup;
    println!("x = {}, y = {}, z = {}", x, y, z);
    // Access tuple elements with dot notation
    println!("First element: {}", tup.0);
    println!("Second element: {}", tup.1);
    // Empty tuple (unit type)
    let unit: () = ();
}

Arrays

Fixed-length collections of values of the same type:

Rust
fn main() {
    // Array with explicit type and length
    let a: [i32; 5] = [1, 2, 3, 4, 5];
    // Array with repeated value
    let b = [3; 5];  // [3, 3, 3, 3, 3]
    // Access array elements
    println!("First element: {}", a[0]);
    println!("Last element: {}", a[4]);
    // Iterate over array
    for element in a.iter() {
        println!("Element: {}", element);
    }
    // Get array length
    println!("Array length: {}", a.len());
}
3

Functions

Functions are fundamental building blocks in Rust. They're declared with the fn keyword and can take parameters and return values.

Basic Function Syntax

Rust
fn add(a: i32, b: i32) -> i32 {
    a + b
fn main() {
    let result = add(5, 3);
    println!("5 + 3 = {}", result);
}

Return Values and Expressions

In Rust, the last expression in a function is automatically returned. Note there's no semicolon on the final expression:

Rust
fn multiply(a: i32, b: i32) -> i32 {
    a * b  // No semicolon - this is a return statement
fn add_one(x: i32) -> i32 {
    x + 1  // This will be returned
fn main() {
    let result1 = multiply(4, 5);
    let result2 = add_one(10);
    println!("4 * 5 = {}", result1);
    println!("10 + 1 = {}", result2);
}
⚠️

Watch the Semicolons!

Adding a semicolon after the last expression turns it into a statement, which means the function returns the unit type () instead of the expected value.

Functions with Multiple Parameters

Rust
fn calculate_rectangle_area(width: f64, height: f64) -> f64 {
    width * height
fn print_coordinates(x: i32, y: i32) {
    println!("Coordinates: ({}, {})", x, y);
fn main() {
    let area = calculate_rectangle_area(15.5, 8.0);
    println!("Area: {}", area);
    print_coordinates(10, 20);
}
4

Control Flow

Rust provides several ways to control the flow of your program: conditionals (if/else) and loops (loop, while, for).

if and else Expressions

Rust
fn check_number(n: i32) {
    if n > 0 {
        println!("{} is positive", n);
    } else if n < 0 {
        println!("{} is negative", n);
    } else {
        println!("{} is zero", n);
    }
fn main() {
    check_number(5);
    check_number(-3);
    check_number(0);
}

Using if as an Expression: Since if is an expression in Rust, you can use it to assign values:

Rust
fn main() {
    let condition = true;
    let number = if condition { 5 } else { 6 };
    println!("The value is: {}", number);  // Prints: The value is: 5
    // Both branches must return the same type
    let age = 25;
    let category = if age >= 18 { "adult" } else { "minor" };
    println!("Category: {}", category);
}

The loop Statement

The loop keyword creates an infinite loop that runs until you explicitly break out:

Rust
fn main() {
    let mut counter = 0;
    loop {
        println!("Counter: {}", counter);
        counter += 1;
        if counter == 3 {
            break;  // Exit the loop
        }
    }
    println!("Loop finished");
}

Returning Values from Loops: You can return a value from a loop by using break with an expression:

Rust
fn main() {
    let mut counter = 0;
    let result = loop {
        counter += 1;
        if counter == 5 {
            break counter * 2;  // Break with a value
        }
    };
    println!("Result: {}", result);  // Prints: Result: 10
}

The while Loop

Rust
fn main() {
    let mut number = 3;
    while number != 0 {
        println!("{}!", number);
        number -= 1;
    }
    println!("LIFTOFF!");
}

The for Loop

The for loop is the most idiomatic way to iterate in Rust:

Rust
fn main() {
    // Iterate over a range
    for i in 1..4 {
        println!("{}!", i);
    }
    // Iterate with inclusive range (includes 5)
    for i in 1..=5 {
        println!("Number: {}", i);
    }
    // Iterate over array
    let arr = [10, 20, 30, 40];
    for element in arr.iter() {
        println!("Element: {}", element);
    }
    // Reverse iteration
    for i in (1..=3).rev() {
        println!("{}!", i);
    }
}
5

Comments

Comments are notes in your code that the compiler ignores. Rust supports single-line and documentation comments:

Rust
// This is a single-line comment
/* This is a multi-line comment
   that can span multiple lines */
/// This is a documentation comment
/// It documents the next item (function, struct, etc.)
/// and appears in generated documentation.
fn documented_function() {
    // Implementation here
//! This is a crate-level or module-level documentation comment
//! It documents the item that contains it.

Documentation Comments are Special

Use /// before functions and structs to generate HTML documentation with cargo doc --open. This is a cornerstone of Rust's excellent documentation ecosystem.

← Previous Chapter 2 of 17 Next →