blog.dopana

Back

Fourth post in the basic Rust series. Functions are how you name and reuse logic. Rust’s functions are simple — the key idea is that almost everything is an expression.

A first function#

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

fn main() {
    let result = add(3, 4);
    println!("{result}");   // 7
}
rust

Parameters declare their types; the return type comes after ->.

Statements vs expressions#

A statement performs an action and returns nothing. An expression evaluates to a value.

fn add(a: i32, b: i32) -> i32 {
    let sum = a + b;   // statement, ends with a semicolon
    sum                // expression, no semicolon
}
rust

The last expression — without a semicolon — is the return value. If you add a semicolon, the function returns () instead.

Early return#

Use return to exit early:

fn is_adult(age: u32) -> bool {
    if age >= 18 {
        return true;
    }
    false
}
rust

No return value#

A function that produces nothing has the unit type ():

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

The implicit return type is ().

Passing ownership#

By default, passing a value to a function moves it (details in the next post on ownership):

fn print_len(s: String) {
    println!("{}", s.len());
}

fn main() {
    let s = String::from("hello");
    print_len(s);   // s is moved
    // println!("{s}");  // error: s no longer exists
}
rust

Generic functions#

Functions can be generic over types with angle brackets:

fn first<T>(list: &[T]) -> &T {
    &list[0]
}
rust

T stands for any type. The compiler infers it from the call site.

Conclusion#

Rust functions are expressions all the way down: parameters are typed, the last expression is the return value, and ownership travels with values. Next in this series: ownership.

References#