[Rust Series 4] Functions and Control Flow

한국어 버전

Sessions 1–3 built up variables and data types; now we can combine values and control execution. Rust defines functions with fn, and because it is expression-oriented, if blocks or plain {} blocks can return values. Session 4 covers function declarations, expressions versus statements, conditional branching, and the major loop forms.

New terms in this post

  1. Expression: Any construct that produces a value; in Rust almost everything is an expression.
  2. Statement: Code that executes but returns no value, such as variable declarations.
  3. loop: An unconditional infinite loop that you exit with break.
  4. for loop: A loop that iterates over something implementing Iterator, like arrays or ranges.

Core ideas

  • Functions follow the form fn name(param: Type) -> ReturnType { ... }. If you omit -> ReturnType, it defaults to () (unit). Parameters must declare their types, so the signature alone tells you what flows through the function.
  • A block {} returns the last expression's value, so you must avoid a trailing semicolon when you mean to return that value.
  • if is itself an expression, so you can do let grade = if score >= 90 { "A" } else { "B" };.
  • Start with three loop forms: loop, while, and for, plus break and continue for fine-grained control.

Code along

1. Function basics

fn main() {
    greet("Mathbong");
    let doubled = double(21);
    println!("Double: {doubled}");
}

fn greet(name: &str) {
    println!("안녕하세요, {name}!");
}

fn double(value: i32) -> i32 {
    value * 2 // no semicolon: expression returns the value
}

Add a semicolon to the final line of double and you will return () instead, causing a type error. Remember: expressions return values, statements do not.

Compare the following two functions:

fn add_ok(a: i32, b: i32) -> i32 {
    a + b
}

fn add_wrong(a: i32, b: i32) -> i32 {
    a + b;
    0
}

The first returns a + b directly. The second turns a + b; into a statement (no return), so you must write a separate final value.

2. Expressions vs. statements

fn main() {
    let statement = {
        let helper = 2;
        helper * 3
    }; // block expression → value 6

    let condition = true;
    let result = if condition { 10 } else { 0 };

    println!("statement = {statement}, result = {result}");
}

Blocks return their last expression, and if expressions require every branch to return the same type.

3. if conditionals

fn main() {
    let score = 87;

    if score >= 90 {
        println!("Grade A");
    } else if score >= 80 {
        println!("Grade B");
    } else {
        println!("Grade C or below");
    }
}

Rust requires a bool condition. Patterns like if score from Python are not allowed.

4. Loops

fn main() {
    let mut count = 0;

    loop {
        count += 1;
        if count == 3 {
            println!("Exiting loop");
            break;
        }
    }

    let mut n = 3;
    while n > 0 {
        println!("while: {n}");
        n -= 1;
    }

    let numbers = [10, 20, 30];
    for value in numbers {
        println!("for: {value}");
    }
}
  • loop runs forever until you break. Use break value; to exit and return a value to bind later.
  • while runs as long as the condition stays true.
  • for iterates over anything implementing IntoIterator. Ranges like 0..5 cover 0 through 4, while 1..=5 includes 5.

Why it matters

  • You need functions and flow control to reason about ownership at function boundaries later.
  • Thinking in expressions makes if, match, and block returns feel natural and sets you up for the ? operator and closures.
  • Loop patterns provide reference points before you graduate to iterators and pattern matching.

Practice

  1. Write fn max(a: i32, b: i32) -> i32 that returns the larger value through an if expression.
  2. Use loop with break value to compute the nth Fibonacci number and store it in a variable.
  3. Iterate in reverse with for number in (1..=5).rev() and observe that ranges are iterators.
  4. Rewrite a function so it returns the last expression instead of using an early return.

Wrap-up

You can now declare functions and steer execution with conditionals and loops, giving you the skeleton for small programs. Session 5 tackles Rust's differentiator: ownership—tracking how values move, when scopes end, and why the compiler cares.

💬 댓글

이 글에 대한 의견을 남겨주세요