OSRust.004: Functions, Parameters, Expressions, and Return Values

Colored-pencil illustration of a programmer learning Rust functions, parameters, expressions, and return values.

Rust functions package behavior into named, reusable units with explicit inputs and outputs. The syntax is compact, but Rust adds an important idea that beginners must understand early: blocks and function bodies are expression-oriented, so the final expression can become the returned value without writing return.

This lesson follows OSRust.003: Scalar and Compound Data Types. OSRust.003 established the types functions will accept and return; OSRust.004 turns those types into reusable program structure.

Tech With Tim demonstrates Rust function definitions, parameters, statements, expressions, and return values.

Elementary Overview

A function is a named piece of code that performs a job. A function can receive values, work with them, and give a value back. Instead of rewriting the same logic in many places, a program can call the function whenever that job is needed.

Learning Objectives

  • Define and call Rust functions with fn.
  • Distinguish parameters from arguments.
  • Explain why Rust function parameters require explicit types.
  • Distinguish statements from expressions.
  • Use tail expressions to return values.
  • Explain the effect of adding a semicolon to a tail expression.
  • Use explicit return for early exits.
  • Recognize the unit type () when no useful value is returned.

Defining and Calling Functions

Rust defines a function with the fn keyword, followed by the function name, parentheses, and a body in braces. Function names conventionally use snake_case. A function can be defined before or after the code that calls it, as long as the definition is visible in the relevant scope.

fn greet() {
    println!("System online");
}

fn main() {
    greet();
}

The function call greet() is an expression. In this example, the function performs an action but does not return a useful data value to the caller, so its return type is the unit type ().

Parameters and Arguments

A parameter is a named input in the function definition. An argument is the concrete value passed when the function is called. Rust requires the type of every function parameter to be written explicitly, which makes function interfaces predictable and improves compiler diagnostics.

fn print_power(watts: u32, rack: u8) {
    println!("Rack {rack}: {watts} W");
}

fn main() {
    print_power(3200, 7);
}

Here watts and rack are parameters. The values 3200 and 7 are arguments supplied by the caller.

Piyush Garg follows Rust Book Chapter 3.3 and covers function definitions, parameters, expressions, statements, and returned values.

Return Types

A function that returns a value declares the output type after ->. The returned value must match that type.

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

fn main() {
    let total = add(20, 22);
    println!("{total}");
}

The final line a + b has no semicolon. It is a tail expression, and its value becomes the function result. This is the normal concise Rust style for returning the final value of a function.

Statements and Expressions

Rust distinguishes statements from expressions. A statement performs an action but does not itself produce a value that can be used as the result of the surrounding block. An expression evaluates to a value. Literals, arithmetic, function calls, blocks, and many control-flow constructs are expressions.

fn main() {
    let result = {
        let base = 10;
        base * 2
    };

    println!("{result}");
}

The block assigned to result evaluates to 20 because its final expression is base * 2. The outer let statement then binds that value to result.

This Rust By Example walkthrough focuses on expressions, blocks, and the semicolon rule that determines whether a block yields a useful value or ().

The Semicolon Changes the Result

The semicolon is one of the most important details in early Rust. Adding a semicolon to the final value-producing expression discards that value and makes the expression evaluate to the unit type ().

fn plus_one(x: i32) -> i32 {
    x + 1
}

This compiles because x + 1 evaluates to an i32. The version below does not satisfy the declared return type:

fn plus_one(x: i32) -> i32 {
    x + 1;
}

The semicolon turns the final expression into a statement, leaving the function body with the value () instead of i32. Rust reports a type mismatch and commonly suggests removing the semicolon.

A Rust community discussion examines why idiomatic Rust usually uses a final expression rather than writing return at the end of every function.

Explicit return Is Still Useful

The return keyword exits the function immediately. It is especially useful for early-exit validation paths, errors, guard clauses, or branches where execution should stop before reaching the tail expression.

fn efficiency_class(j_per_th: f64) -> &'static str {
    if j_per_th <= 10.0 {
        return "high efficiency";
    }

    "standard efficiency"
}

The early return handles one path. The final string literal is the tail expression for the remaining path. Both produce the declared &'static str return type.

Functions Without a Useful Return Value

If no return type is written, the function returns (), called the unit type. It is Rust’s way of representing a successful computation that does not produce a meaningful data value.

fn log_alarm(message: &str) {
    println!("ALARM: {message}");
}

The omitted return type is equivalent to writing -> (). Functions that print, mutate state through permitted references, write to a device, or trigger other side effects commonly return unit.

Practical Exercise

  1. Create a project with cargo new rust_functions_lab.
  2. Write fn watts(volts: f64, amps: f64) -> f64 using a tail expression.
  3. Call it from main and print the result.
  4. Add a second function that accepts a device name and a Boolean online/offline state but returns ().
  5. Create a block expression that calculates a value and assign that block to a variable.
  6. Add a semicolon to the final expression and observe the compiler error or changed type.
  7. Write a function with an early return when an input is invalid.
  8. Run cargo check after each change, then run the finished program with cargo run.

Knowledge Check

  1. Which keyword defines a function in Rust?
  2. What is the difference between a parameter and an argument?
  3. Are function parameter types normally written explicitly in Rust?
  4. How is a function return type declared?
  5. What is a tail expression?
  6. What happens when a semicolon is added to a value-producing tail expression?
  7. What type does a function return when no useful return value is declared?
  8. When is explicit return especially useful?

Answers

  1. fn.
  2. A parameter is a named input in the function definition; an argument is the concrete value supplied in a call.
  3. Yes. Function parameters require explicit type annotations.
  4. With -> Type after the parameter list.
  5. The final expression in a block whose value becomes the value of that block or function.
  6. Its value is discarded and the expression evaluates to ().
  7. The unit type ().
  8. For early exits, guard clauses, validation failures, and paths that must leave the function before the tail expression.

Elementary Conclusion

A Rust function has a name, optional inputs, and an optional output. Parameters tell the function what information it receives. The return type tells callers what comes back. Rust’s unusual but powerful rule is that the final expression can become the answer automatically, so one small semicolon can change whether a value is returned.

Previous Related Lessons

Primary References

BitcoinVersus.Tech Editor’s Note:
We volunteer daily to ensure the credibility of the information on this platform is Verifiably True. If you would like to support to help further secure the integrity of our research initiatives, please donate using the QR code provided

Bitcoin donation QR code for 5,000 sats.

For best QR-code scanning results, please scan this donation code from a desktop or larger screen. Mobile screens may prevent camera recognition.

BitcoinVersus.Tech provides technical education and analysis for informational purposes only.

Leave a Reply