Skip to content

Functions & Control Flow Stack Deep Dive

Mỗi Function Call tạo Stack Frame — Hiểu cách data di chuyển

Function Syntax Basics

rust
fn add(x: i32, y: i32) -> i32 {
    x + y    // Expression - không cần 'return'
}

fn main() {
    let result = add(5, 3);
    println!("Result: {}", result);
}

Quan sát:

  • Parameters phải có type annotations (x: i32)
  • Return type sau -> (bỏ qua nếu trả về ())
  • Không cần return cho expression cuối cùng

Stack Frames: Anatomy of a Function Call

Mỗi function call tạo Stack Frame

rust
fn multiply(a: i32, b: i32) -> i32 {
    let result = a * b;
    result
}

fn main() {
    let x = 5;
    let y = 3;
    let z = multiply(x, y);
}

Stack Evolution During Execution

═══════════════════════════════════════════════════════════════════
STEP 1: main() được gọi
═══════════════════════════════════════════════════════════════════

                    ┌─────────────────────────────────┐  ← Stack Top
                    │        STACK FRAME: main()      │
                    ├─────────────────────────────────┤
                    │  Return Address (to OS/runtime) │
                    ├─────────────────────────────────┤
                    │  x: i32 = 5                     │
                    ├─────────────────────────────────┤
                    │  y: i32 = 3                     │
                    ├─────────────────────────────────┤
                    │  z: i32 = ??? (uninitialized)   │
                    └─────────────────────────────────┘  ← Stack Base
                    
═══════════════════════════════════════════════════════════════════
STEP 2: multiply(x, y) được gọi
═══════════════════════════════════════════════════════════════════

                    ┌─────────────────────────────────┐  ← Stack Top
                    │     STACK FRAME: multiply()     │
                    ├─────────────────────────────────┤
                    │  Return Address (to main)       │
                    ├─────────────────────────────────┤
                    │  a: i32 = 5    (copy of x)      │
                    ├─────────────────────────────────┤
                    │  b: i32 = 3    (copy of y)      │
                    ├─────────────────────────────────┤
                    │  result: i32 = 15               │
                    ├═════════════════════════════════┤
                    │        STACK FRAME: main()      │
                    ├─────────────────────────────────┤
                    │  Return Address                 │
                    ├─────────────────────────────────┤
                    │  x: i32 = 5                     │
                    ├─────────────────────────────────┤
                    │  y: i32 = 3                     │
                    ├─────────────────────────────────┤
                    │  z: i32 = ???                   │
                    └─────────────────────────────────┘  ← Stack Base

═══════════════════════════════════════════════════════════════════
STEP 3: multiply() returns 15
═══════════════════════════════════════════════════════════════════

    Return value (15) được copy về qua register (thường RAX)
    
                                        ┌───────────┐
                    Stack Frame:        │ RAX = 15  │  ← Return value
                    multiply()          └─────┬─────┘
                    [DEALLOCATED]             │
                         ↓                    │
                    ┌────────────────────────────────┐
                    │        STACK FRAME: main()     │
                    ├────────────────────────────────┤
                    │  x: i32 = 5                    │
                    ├────────────────────────────────┤
                    │  y: i32 = 3                    │
                    ├────────────────────────────────┤
                    │  z: i32 = 15  ←────────────────┘ (từ RAX)
                    └────────────────────────────────┘

Key Insights

  1. Arguments được COPY vào stack frame mới (cho Copy types như i32)
  2. Return value qua register (RAX on x86_64) hoặc memory nếu lớn
  3. Stack frame bị deallocate ngay khi function return
  4. LIFO order: Last In, First Out

Statements vs Expressions

Fundamental Difference

ConceptDefinitionReturns Value?Example
StatementThực hiện action❌ Không (())let x = 5;
ExpressionTính toán giá trị✅ Có5 + 3, { x + 1 }

Statements

rust
fn main() {
    let x = 5;      // Statement: let binding
    let y = {       // Statement: let với block expression
        let z = 3;  // Statement bên trong
        z + 1       // Expression - giá trị của block
    };
    
    // ❌ KHÔNG COMPILE:
    // let a = (let b = 5);  // `let` là statement, không có value
}

Expressions are Everywhere

rust
fn main() {
    // Số, operators là expressions
    let a = 5;                    // 5 là expression
    let b = 5 + 3;                // 5 + 3 là expression
    
    // Blocks {} là expressions
    let c = {
        let x = 10;
        x * 2       // KHÔNG có semicolon → đây là return value của block
    };              // c = 20
    
    // if/else là expression
    let d = if true { 1 } else { 0 };  // d = 1
    
    // match là expression
    let e = match 5 {
        0 => "zero",
        _ => "non-zero",
    };  // e = "non-zero"
    
    // Function call là expression
    let f = add(1, 2);  // f = 3
}

fn add(x: i32, y: i32) -> i32 {
    x + y   // Expression, KHÔNG có semicolon
}

Semicolon Rules

rust
fn returns_five() -> i32 {
    5       // ✅ Expression - returns 5
}

fn returns_unit() -> () {
    5;      // Statement - returns () vì có semicolon
}

// ❌ KHÔNG COMPILE:
// fn broken() -> i32 {
//     5;   // Error: expected `i32`, found `()`
// }

Memory View:

returns_five():                    returns_unit():
┌────────────────┐                ┌────────────────┐
│  Expression: 5 │───→ RAX       │  Statement: 5; │───→ () (nothing)
│  (no semicolon)│                │  (semicolon)   │
└────────────────┘                └────────────────┘
     Returns i32                       Returns ()

Control Flow: if Expression

if trong Rust là Expression

rust
fn main() {
    let condition = true;
    
    // Traditional (statement-like) usage
    if condition {
        println!("true branch");
    } else {
        println!("false branch");
    }
    
    // Expression usage (giống ternary trong C)
    let number = if condition { 5 } else { 6 };
    
    // ❌ KHÔNG COMPILE: Type mismatch
    // let bad = if condition { 5 } else { "six" };
}

Tại sao branches phải cùng type?

if condition { 5 } else { 6 }
     ↓            ↓
   i32          i32    ✅ OK

if condition { 5 } else { "six" }
     ↓            ↓
   i32          &str   ❌ Compiler Error!

Compiler cần biết type của expression tại compile time để allocate stack space.


Control Flow: Loops

loop — Infinite Loop with Return

rust
fn main() {
    let mut counter = 0;
    
    let result = loop {
        counter += 1;
        
        if counter == 10 {
            break counter * 2;  // Return value từ loop
        }
    };
    
    println!("Result: {}", result);  // 20
}

while — Conditional Loop

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

for — Iterator Loop

rust
fn main() {
    let arr = [10, 20, 30, 40, 50];
    
    // Iterate by reference (không move ownership)
    for element in arr.iter() {
        println!("{}", element);
    }
    
    // Range syntax
    for i in 0..5 {
        println!("Index: {}", i);
    }
    
    // Enumerate
    for (index, value) in arr.iter().enumerate() {
        println!("arr[{}] = {}", index, value);
    }
}

Stack allocation cho loop:

for i in 0..5 { ... }

Loop iteration 0:
┌────────────────┐
│ i: usize = 0   │
└────────────────┘

Loop iteration 1:
┌────────────────┐
│ i: usize = 1   │  ← Same stack slot, overwritten
└────────────────┘

...

match — Pattern Matching (An toàn hơn switch-case)

Cú pháp cơ bản

rust
fn main() {
    let number = 3;
    
    match number {
        1 => println!("One"),
        2 => println!("Two"),
        3 => println!("Three"),
        _ => println!("Something else"),  // Wildcard - catch-all
    }
}

Tại sao match an toàn hơn switch-case?

1. Exhaustive Matching — Compiler bắt buộc handle ALL cases

rust
enum Direction {
    North,
    South,
    East,
    West,
}

fn describe(dir: Direction) -> &'static str {
    match dir {
        Direction::North => "Going north",
        Direction::South => "Going south",
        Direction::East => "Going east",
        // ❌ KHÔNG COMPILE: missing `Direction::West`
    }
}

// ✅ ĐÚNG: Handle tất cả variants
fn describe_complete(dir: Direction) -> &'static str {
    match dir {
        Direction::North => "Going north",
        Direction::South => "Going south",
        Direction::East => "Going east",
        Direction::West => "Going west",
    }
}

So sánh với C:

c
// C switch - KHÔNG có exhaustive checking
switch (dir) {
    case NORTH: return "North";
    case SOUTH: return "South";
    // Quên EAST và WEST → undefined behavior hoặc fallthrough
}

2. No Fallthrough — Mỗi arm tách biệt

rust
fn main() {
    let x = 1;
    
    match x {
        1 => println!("One"),
        2 => println!("Two"),   // KHÔNG fallthrough từ 1
        _ => println!("Other"),
    }
}

So sánh với C:

c
switch (x) {
    case 1:
        printf("One");
        // BUG: quên break → fallthrough sang case 2!
    case 2:
        printf("Two");
        break;
}

3. Pattern Destructuring — Extract values

rust
fn main() {
    let point = (3, 5);
    
    match point {
        (0, 0) => println!("Origin"),
        (x, 0) => println!("On x-axis at {}", x),
        (0, y) => println!("On y-axis at {}", y),
        (x, y) => println!("At ({}, {})", x, y),
    }
}

4. Guards — Extra conditions

rust
fn main() {
    let num = Some(4);
    
    match num {
        Some(x) if x < 5 => println!("Less than five: {}", x),
        Some(x) => println!("Greater or equal to five: {}", x),
        None => println!("No value"),
    }
}

match với Ranges

rust
fn main() {
    let age: u8 = 25;
    
    match age {
        0..=12 => println!("Child"),
        13..=19 => println!("Teenager"),
        20..=59 => println!("Adult"),
        60..=u8::MAX => println!("Senior"),
    }
}

match là Expression

rust
fn main() {
    let x = 5;
    
    let description = match x {
        1 => "one",
        2 => "two",
        3 => "three",
        _ => "many",
    };
    
    println!("{}", description);  // "many"
}

Stack Frame với Control Flow

Nested Scopes

rust
fn main() {
    let a = 5;
    {
        let b = 10;
        {
            let c = 15;
            println!("{} {} {}", a, b, c);
        }  // c dropped here
    }  // b dropped here
}  // a dropped here

Stack Evolution:

Bước 1: let a = 5
┌─────────────────┐
│ a: i32 = 5      │
└─────────────────┘

Bước 2: Enter inner scope, let b = 10
┌─────────────────┐
│ b: i32 = 10     │  ← Inner scope
├─────────────────┤
│ a: i32 = 5      │
└─────────────────┘

Bước 3: Enter innermost scope, let c = 15
┌─────────────────┐
│ c: i32 = 15     │  ← Innermost scope
├─────────────────┤
│ b: i32 = 10     │
├─────────────────┤
│ a: i32 = 5      │
└─────────────────┘

Bước 4: Exit innermost scope (c dropped)
┌─────────────────┐
│ b: i32 = 10     │
├─────────────────┤
│ a: i32 = 5      │
└─────────────────┘

Bước 5: Exit inner scope (b dropped)
┌─────────────────┐
│ a: i32 = 5      │
└─────────────────┘

Function Parameters: Move vs Copy

Copy Types (primitives)

rust
fn takes_i32(x: i32) {
    println!("{}", x);
}

fn main() {
    let num = 42;
    takes_i32(num);     // num được COPY vào function
    println!("{}", num); // ✅ num vẫn valid
}

Move Types (heap-allocated)

rust
fn takes_string(s: String) {
    println!("{}", s);
}  // s dropped here

fn main() {
    let hello = String::from("hello");
    takes_string(hello);     // hello được MOVE vào function
    // println!("{}", hello); // ❌ ERROR: value borrowed after move
}

Stack visualization:

MOVE với String:

main() TRƯỚC call:               takes_string() stack frame:
┌─────────────────────┐          ┌─────────────────────┐
│ hello: String       │    →     │ s: String           │
│   ptr ──────────────────────→  │   ptr ──────────────────→ "hello" (heap)
│   len: 5            │          │   len: 5            │
│   cap: 5            │          │   cap: 5            │
└─────────────────────┘          └─────────────────────┘
     [invalidated]                    [owns heap data]

Bảng Tóm tắt

ConceptKey Point
Stack FrameĐược tạo mỗi function call, chứa locals và return address
ExpressionTính toán và trả về value, KHÔNG có semicolon cuối
StatementThực hiện action, có semicolon, trả về ()
if is expressionBranches phải cùng type
match exhaustiveCompiler bắt buộc handle ALL cases
match no fallthroughMỗi arm tách biệt, không cần break
Move vs CopyPrimitives copy, heap types move

Bài tập kiểm tra

💪 Bài 1: Expression hay Statement?
rust
let x = 5;       // A
5 + 3            // B
{ let y = 1; }   // C
{ let y = 1; y } // D
Đáp án
  • A: Statement (let binding)
  • B: Expression (arithmetic)
  • C: Expression trả về () (block có statement bên trong, không có expression cuối)
  • D: Expression trả về i32 (block có expression cuối y)
💪 Bài 2: Fix lỗi compile
rust
fn main() {
    let result = if true {
        5
    } else {
        "five"
    };
}
Đáp án

Branches phải cùng type:

rust
// Cách 1: Cả hai trả về i32
let result = if true { 5 } else { 0 };

// Cách 2: Cả hai trả về &str
let result = if true { "5" } else { "five" };

// Cách 3: Generic approach (không khuyến khích)
// Dùng enum hoặc trait object