DaleSchool

Writing Functions

Beginner15min

Learning Objectives

  • Define functions using the fn keyword
  • Pass parameters to functions
  • Return values from functions
  • Understand the difference between expressions and statements

Working Code

Try running the code below in the Rust Playground!

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

fn main() {
    greet("DaleSchool");
    greet("Rust");
}

You can create functions with fn. Here we made a function called greet and called it twice from main.

Now let's look at a function that returns a value.

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

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

-> i32 means "this function returns a value of type i32." If you write a value on the last line of a function without a semicolon, that value gets returned.

Try It Yourself

Try adding a semicolon (;) after a + b in the code below. What error do you get?

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

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

Adding a semicolon causes an error; removing it makes the code work. Let's find out why below!

"Why?" — Expressions vs. Statements

In Rust, there's a distinction between expressions and statements.

  • Expressions produce a value. Things like 3 + 5 and a + b are expressions.
  • Statements don't produce a value. Adding a semicolon (;) turns something into a statement.
fn double(x: i32) -> i32 {
    x * 2   // No semicolon -> expression -> this value gets returned
}

fn main() {
    let result = double(7);
    println!("Double of 7: {result}");
}

If the last line of a function is an expression, its value is automatically returned. If you add a semicolon, it becomes a statement and returns nothing.

One more thing! Function parameters must always have explicit types. While let bindings can rely on type inference, function parameters cannot omit types.

fn is_even(number: i32) -> bool {
    number % 2 == 0
}

fn main() {
    println!("Is 4 even? {}", is_even(4));
    println!("Is 7 even? {}", is_even(7));
}

Deep Dive

There's also a return keyword

Rust has a return keyword too. You use it when you want to return a value early, in the middle of a function.

fn check_age(age: i32) -> &'static str {
    if age < 0 {
        return "Age must be 0 or greater";
    }
    "Valid input"
}

fn main() {
    println!("{}", check_age(-1));
    println!("{}", check_age(20));
}

For the last line, you can simply omit the semicolon instead of writing return. In Rust, leaving out return is the more idiomatic style.

If a function has a return type, adding a semicolon to the last expression causes an error.

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

fn main() {
    println!("{}", add(1, 2));
}

Error message:

error[E0308]: mismatched types
 --> src/main.rs:1:32
  |
1 | fn add(a: i32, b: i32) -> i32 {
  |    ---                     ^^^ expected `i32`, found `()`
  |    |
  |    implicitly returns `()` as its body has no tail expression
2 |     a + b;
  |          - help: remove this semicolon to return this value

What this means: "You said the function returns i32, but it's actually returning nothing." Removing the semicolon turns a + b back into an expression, so the value gets returned.

Look at the last line of the error message — help: remove this semicolon to return this value. The compiler is even telling you how to fix it!

  1. Write a max function that takes two i32 parameters and returns the larger one. (Hint: if a > b { a } else { b })
  2. Write a square function that takes a single i32 parameter and returns its squared value, then call it from main.

Q1. What does the following function return?

fn mystery(x: i32) -> i32 {
    x * 3
}

What happens when you call mystery(4)?

  • A) 3
  • B) 4
  • C) 12
  • D) Compilation error

Q2. Can you omit the type of a function parameter in Rust?

  • A) You can always omit it
  • B) You can never omit it
  • C) You can omit it if the compiler can infer the type
  • D) You can omit it if you add mut

Q3. What is the output of this code?

fn half(x: i32) -> i32 {
    x / 2
}

fn main() {
    println!("{}", half(7));
}
  • A) 3.5
  • B) 3
  • C) 4
  • D) Compilation error