[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 in CodeSandbox

The sandbox below uses CodeSandbox's Rust starter. Move the main code into src/main.rs, then compare cargo check and cargo run so you can read the compiler feedback beside the final output.

Live Practice

Rust Practice Sandbox

CodeSandbox

Run the starter project in CodeSandbox, compare it with the lesson code, and keep experimenting.

Rust startercargoterminal
  1. Fork the starter and open src/main.rs
  2. Paste the lesson code and run cargo check plus cargo run in order
  3. Change types, values, or borrowing flow and compare the compiler feedback with the output

Rust practice here is mainly terminal-driven rather than browser-preview driven. Lessons that need multiple files or extra crates may require a bit more setup inside the starter.

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.

💬 댓글

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