| |

Rust 7 ๐Ÿฆ€ Functions, Parameters, and Return Values

Functions are the building blocks of Rust programs. Every Rust program has at least one โ€” main โ€” and most programs are organized as a collection of functions that call each other. A function packages a sequence of statements behind a name, accepts inputs as parameters, produces an output as a return value, and can be called from anywhere that has access to it. Rust’s function syntax follows the C-family convention with fn, a name, a parameter list, and a return type, but the details are stricter than most languages: parameter types are always required, the return type is explicit unless it is the unit type, and the body is a block that evaluates to a value.

That last point is the most distinctive feature. In Rust, a block is an expression, and the last expression in a function body becomes the return value. There is no need for a return keyword in the common case โ€” the absence of a semicolon on the final expression is what returns it. This “everything is an expression” model runs through the language and is one of the first things that surprises programmers coming from statement-oriented languages.

This chapter covers function definitions, parameters and their types, return values and the expression-based body, the return keyword and early exits, statements versus expressions, nested functions, the main function, and the conventions that make functions readable.

Key point: Rust functions are declared with fn, parameters always require type annotations, and the return type is written after ->. The body is a block; its final expression is the return value if it has no trailing semicolon. Adding a semicolon turns the expression into a statement and changes the function to return ().


Why functions are structured this way

The explicit-types problem. Rust is statically typed and does not perform type inference across function boundaries. A function’s parameter and return types are part of its public interface, and the compiler checks every call against that interface. This means the caller can type-check a call without reading the function body, and the function can be compiled without knowledge of its callers. Explicit types in signatures are what make separate compilation possible.

The expression problem. In Rust, almost everything is an expression. if, match, blocks, and loops all produce values. A function body is a block, and a block’s value is its final expression. This uniform model means there is no separate “return statement” for the common case โ€” the value of the block is the value of the function. The return keyword exists for early exits, but it is not the primary mechanism.

The semicolon problem. The difference between an expression and a statement in Rust is the semicolon. 5 is an expression with the value 5. 5; is a statement with the value (). A function whose body ends with 5; returns (), not 5. This is the single most common mistake for newcomers, and it produces a type error that names the expected and found types.

The unit problem. Functions that perform an action rather than computing a value return the unit type (). The return type can be omitted when it is (). This keeps the syntax for side-effecting functions (like println!) clean while making the absence of a value explicit in the type system.

The recursion and forward-reference problem. Rust functions can call each other regardless of declaration order. A function can call another function defined later in the same file, and mutually recursive functions work without forward declarations. The compiler resolves names across the whole module.


a. Defining and calling functions

A function is defined with the fn keyword, a name in snake_case, a parameter list, an optional return type, and a body in braces.

fn greet() {
    println!("Hello!");
}

fn main() {
    greet();
    greet();
}

The name follows snake_case: lowercase words separated by underscores. The compiler warns if a function name is not in snake_case, though it does not error.

Calling a function uses its name followed by parentheses with the arguments:

fn main() {
    greet();   // no arguments
}

Functions can be defined in any order. main can call a function defined after it in the file, and functions can call each other without forward declarations.

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

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

b. Parameters and type annotations

Every parameter requires a type annotation. There is no inference for parameters; the function’s signature must be fully explicit.

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

The parameter name is followed by a colon and the type. Multiple parameters are separated by commas.

fn describe(name: &str, age: u32, active: bool) {
    println!("{} is {} and active: {}", name, age, active);
}

A parameter can be shadowed inside the function body, but the original value is not changed for the caller.

fn double(x: i32) -> i32 {
    let x = x * 2;   // new binding, shadows parameter
    x
}

Functions can take references as parameters, which is the standard way to pass values without transferring ownership:

fn length(s: &str) -> usize {
    s.len()
}

fn main() {
    let name = String::from("Alice");
    let n = length(&name);   // pass a reference
    println!("{}", n);       // name is still usable
}

The distinction between String and &str, and between owned values and references, is central to Rust’s ownership model. The convention is to take &str rather than &String for string parameters, and &[T] rather than &Vec<T> for slice parameters, because the reference types are more general and accept more inputs.

// Accepts &str, which includes string literals and &String
fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}

greet("Alice");                        // &str literal
greet(&String::from("Bob"));           // &String coerces to &str

c. Return values and the expression body

A function’s return type is written after ->. The body is a block, and the value of the block is the value of the function.

fn add(a: i32, b: i32) -> i32 {
    a + b     // no semicolon: this is the return value
}

The final expression must match the declared return type. If it does not, the compiler reports a type mismatch.

fn add(a: i32, b: i32) -> i32 {
    a + b;    // semicolon: this is a statement; function returns ()
              // error: expected i32, found ()
}

This is the semicolon rule. Without a semicolon, the expression is the block’s value. With a semicolon, the expression becomes a statement and the block’s value becomes ().

A function can have multiple statements before the final expression:

fn area(width: u32, height: u32) -> u32 {
    let product = width * height;   // statement
    product                          // expression: return value
}

The function body evaluates each statement in order, and the final expression’s value is returned.

For functions that return nothing, the return type is () and can be omitted:

fn greet(name: &str) {
    println!("Hello, {}!", name);
}

This is equivalent to:

fn greet(name: &str) -> () {
    println!("Hello, {}!", name);
}

The explicit form is rarely written; omitting -> () is the convention.


d. The return keyword and early exits

The return keyword exits the function immediately with the given value. It is used for early exits, usually when a condition is not met.

fn divide(a: f64, b: f64) -> f64 {
    if b == 0.0 {
        return 0.0;   // early exit
    }
    a / b             // normal exit
}

A guard clause with return flattens nested conditionals:

fn describe(value: Option<i32>) -> String {
    let value = match value {
        Some(v) => v,
        None => return String::from("none"),
    };
    format!("value: {}", value)
}

The return is not needed for the final expression, and the Rust style guide prefers the expression form when the return would be at the end of the function:

// Preferred
fn add(a: i32, b: i32) -> i32 {
    a + b
}

// Also valid, but discouraged
fn add(a: i32, b: i32) -> i32 {
    return a + b;
}

The return is appropriate for early exits, not for the final expression. Using it for both is valid but not idiomatic.


e. Statements versus expressions

The distinction between statements and expressions is fundamental in Rust. An expression produces a value; a statement performs an action. The body of a function is a sequence of statements followed by an optional final expression.

Statements include:

  • let bindings: let x = 5;
  • Expression statements: foo();
  • Item declarations: fn, struct, use

Expressions include:

  • Literals: 5, "hello", true
  • Arithmetic: a + b
  • Function calls: foo()
  • Blocks: { let x = 5; x + 1 }
  • Control flow that produces values: if, match, loops

A block is an expression:

let y = {
    let x = 3;
    x + 1     // block value
};
println!("{}", y);   // 4

This is why the function body works the way it does. The body is a block, and the block’s value is the function’s return value.

A common pattern in Rust is to use the final expression of a match or if as the function’s return:

fn sign(n: i32) -> i32 {
    if n > 0 {
        1
    } else if n < 0 {
        -1
    } else {
        0
    }
}

The if/else is an expression, and its value is returned. No return keyword and no temporary variable are needed.


f. Nested functions and function items

Rust allows functions to be defined inside other functions. A nested function is a separate item, not a closure, and does not capture variables from the enclosing scope.

fn main() {
    fn helper(x: i32) -> i32 {
        x * 2
    }

    println!("{}", helper(5));   // 10
}

Nested functions are useful for organizing a large function’s logic without exposing helpers to the rest of the module. They cannot access local variables of the enclosing function; if they need to, a closure is required.

Functions are first-class values in Rust. A function can be assigned to a variable, passed as an argument, and returned from another function. The type of a function item is written with fn:

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

fn apply(f: fn(i32, i32) -> i32, a: i32, b: i32) -> i32 {
    f(a, b)
}

fn main() {
    let result = apply(add, 2, 3);
    println!("{}", result);   // 5
}

The fn(i32, i32) -> i32 type is a function pointer. Closures have a different type, but they can be passed to functions that accept generic types implementing the Fn traits.


Complete Example Session

// ============================================
// PART 1: BASIC FUNCTION DEFINITION
// ============================================
fn greet() {
    println!("Hello!");
}

fn main() {
    greet();
}
// ============================================
// PART 2: FUNCTION WITH PARAMETERS
// ============================================
fn greet(name: &str) {
    println!("Hello, {}!", name);
}

fn main() {
    greet("Alice");
    greet("Bob");
}
// ============================================
// PART 3: FUNCTION WITH RETURN VALUE
// ============================================
fn add(a: i32, b: i32) -> i32 {
    a + b   // no semicolon: this is the return value
}

fn main() {
    let result = add(2, 3);
    println!("{}", result);   // 5
}
// ============================================
// PART 4: THE SEMICOLON RULE
// ============================================
fn add_broken(a: i32, b: i32) -> i32 {
    a + b;   // semicolon makes this a statement
             // function now returns (), type mismatch
}

// Uncomment to see the compile error:
// fn main() {
//     let x = add_broken(1, 2);
// }
// ============================================
// PART 5: MULTIPLE STATEMENTS AND FINAL EXPRESSION
// ============================================
fn area(width: u32, height: u32) -> u32 {
    let product = width * height;
    let adjusted = product + 1;
    adjusted
}

fn main() {
    println!("{}", area(3, 4));   // 13
}
// ============================================
// PART 6: EARLY RETURN
// ============================================
fn divide(a: f64, b: f64) -> f64 {
    if b == 0.0 {
        return 0.0;
    }
    a / b
}

fn main() {
    println!("{}", divide(10.0, 2.0));   // 5
    println!("{}", divide(10.0, 0.0));   // 0
}
// ============================================
// PART 7: BLOCK AS EXPRESSION
// ============================================
fn main() {
    let y = {
        let x = 3;
        x + 1
    };
    println!("{}", y);   // 4
}
// ============================================
// PART 8: IF/ELSE AS RETURN VALUE
// ============================================
fn sign(n: i32) -> i32 {
    if n > 0 {
        1
    } else if n < 0 {
        -1
    } else {
        0
    }
}

fn main() {
    println!("{}", sign(42));    // 1
    println!("{}", sign(-42));   // -1
    println!("{}", sign(0));     // 0
}
// ============================================
// PART 9: NESTED FUNCTION
// ============================================
fn main() {
    fn square(x: i32) -> i32 {
        x * x
    }

    let n = 5;
    println!("{} squared is {}", n, square(n));   // 25
}
// ============================================
// PART 10: FUNCTION AS PARAMETER
// ============================================
fn add(a: i32, b: i32) -> i32 { a + b }
fn multiply(a: i32, b: i32) -> i32 { a * b }

fn apply(f: fn(i32, i32) -> i32, a: i32, b: i32) -> i32 {
    f(a, b)
}

fn main() {
    println!("{}", apply(add, 2, 3));        // 5
    println!("{}", apply(multiply, 2, 3));   // 6
}

These ten parts cover basic functions, parameters, return values, the semicolon rule, multiple statements, early returns, blocks as expressions, if/else as return values, nested functions, and function pointers as parameters.


Quick Reference

Function Syntax

PartExample
Declarationfn name() {}
Parametersfn name(x: i32, y: &str) {}
Return typefn name() -> i32
Body{ ... }
Final expressionLast expression without semicolon
Early returnreturn value;
Unit returnfn name() {} or fn name() -> ()

Parameter Types

TypePurpose
i32, f64, boolCopy types, passed by value
&strString slice reference
&[T]Slice reference
&mut TMutable reference
TOwned value, transferred
impl TraitGeneric parameter with trait bound

Return Value Rules

FormResult
a + bReturns a + b
a + b;Returns ()
return a + b;Returns a + b early
if x { 1 } else { 2 }Returns 1 or 2
match x { ... }Returns match value

Statements vs Expressions

KindExamplesProduces Value
Statementlet x = 5;, foo();No
Expression5, a + b, { ... }, if ...Yes

Best Practices

โœ… Do This:

fn add(a: i32, b: i32) -> i32 {          // Final expression, no return
    a + b
}

fn greet(name: &str) {                    // Omit -> () for side effects
    println!("Hello, {}!", name);
}

fn find(items: &[i32], target: i32) -> Option<usize> {
    for (i, &v) in items.iter().enumerate() {
        if v == target {
            return Some(i);               // Early return for found case
        }
    }
    None                                  // Final expression
}

fn describe(name: &str) -> String {      // Take &str, not &String
    format!("User: {}", name)
}

โŒ Don’t Do This:

fn add(a: i32, b: i32) -> i32 {
    a + b;                                // โŒ Semicolon changes return to ()
}

fn add(a: i32, b: i32) -> i32 {
    return a + b;                         // โŒ return at end is unnecessary
}

fn greet(name: String) {                  // โŒ Owns string unnecessarily
    println!("{}", name);
}

fn process() -> i32 {
    let x = 5;                            // โŒ Missing final expression
}

Common Pitfalls

PitfallWhy It HappensFix
Return type mismatch () vs TSemicolon on final expressionRemove the semicolon
Unused return warningreturn at function endUse expression form
Parameter type missingRust requires explicit typesAdd : Type to parameter
Ownership error on parameterPassing owned StringPass &str instead
Nested function cannot captureFunction items do not captureUse a closure
Function item not a pointerDifferent typesUse fn type for pointers

Real-World Examples

1. Simple Calculator

fn add(a: f64, b: f64) -> f64 { a + b }
fn subtract(a: f64, b: f64) -> f64 { a - b }
fn multiply(a: f64, b: f64) -> f64 { a * b }

2. Guard Clause

fn process(input: &str) -> Result<usize, String> {
    if input.is_empty() {
        return Err(String::from("empty input"));
    }
    Ok(input.len())
}

3. Multiple Return Values

fn min_max(values: &[i32]) -> (i32, i32) {
    let mut min = values[0];
    let mut max = values[0];
    for &v in values {
        if v < min { min = v; }
        if v > max { max = v; }
    }
    (min, max)
}

4. Early Return in Loop

fn find_first_even(values: &[i32]) -> Option<i32> {
    for &v in values {
        if v % 2 == 0 {
            return Some(v);
        }
    }
    None
}

5. If/Else as Return

fn grade(score: u32) -> char {
    if score >= 90 { 'A' }
    else if score >= 80 { 'B' }
    else if score >= 70 { 'C' }
    else { 'F' }
}

6. Match as Return

fn describe(n: i32) -> &'static str {
    match n {
        0 => "zero",
        n if n > 0 => "positive",
        _ => "negative",
    }
}

7. Function Pointer

fn apply(f: fn(i32) -> i32, x: i32) -> i32 {
    f(x)
}

8. Nested Helper

fn calculate(x: i32) -> i32 {
    fn square(n: i32) -> i32 { n * n }
    square(x) + x
}

9. Unit Return

fn log(message: &str) {
    println!("[LOG] {}", message);
}

10. Recursive Function

fn factorial(n: u32) -> u32 {
    if n <= 1 {
        1
    } else {
        n * factorial(n - 1)
    }
}

Visual

Function Anatomy

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  fn  add  (a: i32, b: i32)  ->  i32  {                       โ”‚
โ”‚  โ”‚   โ”‚     โ”‚                  โ”‚      โ”‚                       โ”‚
โ”‚  โ”‚   โ”‚     โ”‚                  โ”‚      โ””โ”€โ”€ Body block          โ”‚
โ”‚  โ”‚   โ”‚     โ”‚                  โ””โ”€โ”€ Return type                โ”‚
โ”‚  โ”‚   โ”‚     โ””โ”€โ”€ Parameters with types                         โ”‚
โ”‚  โ”‚   โ””โ”€โ”€ Name in snake_case                                  โ”‚
โ”‚  โ””โ”€โ”€ Keyword                                                 โ”‚
โ”‚                                                              โ”‚
โ”‚      a + b                                                   โ”‚
โ”‚      โ””โ”€โ”€ Final expression (no semicolon = return value)     โ”‚
โ”‚  }                                                           โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Semicolon Rule

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  fn add(a: i32, b: i32) -> i32 {                             โ”‚
โ”‚      a + b     โ† expression; value is i32                    โ”‚
โ”‚  }             โ† returns i32 โœ…                              โ”‚
โ”‚                                                              โ”‚
โ”‚  fn add(a: i32, b: i32) -> i32 {                             โ”‚
โ”‚      a + b;    โ† statement; value is ()                      โ”‚
โ”‚  }             โ† returns () โŒ type mismatch                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Statements vs Expressions

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  STATEMENTS (no value)         EXPRESSIONS (produce value)   โ”‚
โ”‚                                                              โ”‚
โ”‚  let x = 5;                    x + 5                         โ”‚
โ”‚  foo();                        5                             โ”‚
โ”‚  fn f() {}                     { let y = 3; y + 1 }          โ”‚
โ”‚                                if x > 0 { 1 } else { 0 }     โ”‚
โ”‚                                match x { ... }               โ”‚
โ”‚                                                              โ”‚
โ”‚  Function bodies are blocks. The final expression's value    โ”‚
โ”‚  becomes the return value. A semicolon makes it a statement  โ”‚
โ”‚  and the return value becomes ().                            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Return Paths

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  fn find(items: &[i32], target: i32) -> Option<usize> {      โ”‚
โ”‚                                                              โ”‚
โ”‚      for (i, &v) in items.iter().enumerate() {               โ”‚
โ”‚          if v == target {                                    โ”‚
โ”‚              return Some(i);   โ”€โ”€โ”€ early exit โ”€โ”€โ–ถ             โ”‚
โ”‚          }                                                   โ”‚
โ”‚      }                                                       โ”‚
โ”‚                                                              โ”‚
โ”‚      None                       โ”€โ”€โ”€ normal exit โ”€โ”€โ–ถ          โ”‚
โ”‚  }                                                           โ”‚
โ”‚                                                              โ”‚
โ”‚  Both paths return Option<usize>. The compiler verifies      โ”‚
โ”‚  that all paths return the declared type.                    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
Declarationfn name(params) -> ReturnType { body }
Name conventionsnake_case
Parameter typesAlways required
Return type-> T; omitted for ()
Final expressionReturn value if no semicolon
Semicolon on finalChanges return to ()
Early returnreturn value;
Unit type() for functions with no return
Nested functionfn inside fn, no capture
Function pointerfn(i32) -> i32
RecursionSupported; no forward declaration needed

Key takeaways:

  • Parameter types are always required. Rust does not infer types across function boundaries; the signature must be fully explicit.
  • The return type follows ->. It can be omitted only when the function returns (). The compiler checks that every path returns the declared type.
  • A function body is a block, and the block’s value is the return. The final expression, without a semicolon, is the return value. This is the idiomatic form for simple returns.
  • The semicolon rule is the most common mistake. Adding a semicolon to the final expression turns it into a statement and changes the return type to (). The error message names the mismatch.
  • return is for early exits. It is not needed at the end of a function and is discouraged there. Use it when a condition causes the function to exit before reaching the final expression.
  • Blocks are expressions. let y = { let x = 3; x + 1 }; is valid, and the block’s value is 4. This is why if/else and match can be the final expression of a function.
  • Functions can be nested. A nested function is a separate item and does not capture the enclosing scope. For capture, use a closure.
  • Functions are values. A function can be assigned to a variable, passed as an argument, and returned from another function. The fn type describes a function pointer.

Remember: Functions in Rust are explicit about their interface. Parameters are typed, the return type is declared, and the body is a block whose value is the result. The most distinctive feature is the expression-based body: the last expression without a semicolon is the return value, and adding a semicolon changes the return type to (). This is not a quirk; it is a consequence of Rust’s uniform expression model, where blocks, if, and match all produce values. The return keyword exists for early exits, and guard clauses use it to flatten nesting. Functions can be nested, passed as arguments, and returned from other functions. Once you internalize the semicolon rule, function bodies become natural to read: a sequence of statements followed by an expression that is the result.



Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!