Functions

Write a Rust Function

Rust by example - Functions

Functions are fundamental building blocks in Rust, used to encapsulate reusable blocks of code. Functions are defined with fn, followed by the function name, optional parameters between parentheses (...), optional -> followed by the return type, and curly braces { ... } for the function body. Rust uses snake_case for function names by convention:

Here are examples of functions with or without parameters or return value:


/// Function with no parameters, no return type.
fn say_hello() {
    println!("Hello, Rust!");
}

/// Function with one parameter called `name` of type `&str` (string slice).
fn greet(name: &str) {
    println!("Hello, {}!", name);
}

// Function with two parameters: `a` and `b`, both of type `i32`.
fn add_and_display(a: i32, b: i32) {
    let sum = a + b;
    println!("{} + {} = {}", a, b, sum);
}

/// Function that takes two `i32` values and returns their sum as an `i32`.
fn multiply(x: i32, y: i32) -> i32 {
    x * y // No semicolon: this expression's value is returned.
}

/// Function bodies follow the same rules than blocks:
fn foo(x: i32) -> i32 {
    let y = {
        let z = 3;
        x + z // Blocks return the value of their last expression, if there is no semi-colon.
    };
    y // Similarly, functions return the value of their last expression, if there is no semi-colon.
}

/// Function that implicitly returns the unit type `()`.
/// This is equivalent to `fn print_coordinates(x: i32, y: i32) -> ()`.
fn print_coordinates(x: i32, y: i32) {
    println!("Coordinates: ({}, {})", x, y); // Note the semicolon.
    // No last expression needed to return `()`.
}

/// Function using an explicit `return` keyword (rarely needed).
fn find_first_even(numbers: &[i32]) -> Option<i32> {
    for &num in numbers {
        if num % 2 == 0 {
            return Some(num); // Return early, breaking out of the `for` loop.
        }
    }
    None // Return `None` if no even number is found (last expression).
}

fn main() {
    // Call the functions above.
    say_hello();
    greet("Rustacean");
    add_and_display(5, 3);
    println!("2 * 3 = {}", multiply(2, 3));
    println!("Foo: {}", foo(1));
    print_coordinates(1, 2);
    println!(
        "First even number: {:?}",
        find_first_even(vec![1, 2, 3].as_slice())
    );
}

Generic Functions

Rust by example - generic functions

Functions can be generic, meaning they can operate on parameters of various types without needing to be rewritten for each type:


// This is a generic function that can take any type.
// The type parameter `T` is written between < and >.
// It often can be inferred from the argument passed to the function.
fn generic<T>(_: T) {
    println!("Argument type: {}", std::any::type_name::<T>());
}

/// A generic function can take multiple type parameters.
///
/// This function takes two arguments of potentially different types:
fn take_two<T, U>(_a: T, _b: U) {
    println!(
        "Argument types: {} {}",
        std::any::type_name::<T>(),
        std::any::type_name::<U>()
    );
}

fn main() {
    // The type parameter `T` is inferred to be `char` in this case.
    generic('a');

    // The type parameter `T` is inferred to be `i32` in this case.
    generic(1);

    // We can also explicitly specify the type parameter `char` for `generic()`.
    // Note the use of the 'turbofish' notation: `::<>`
    generic::<char>('a');

    take_two(Some(3), "4");
}

Type parameters can be constrained to implement specified traits:

use std::fmt::Debug;
use std::fmt::Display;

/// Generic functions can accept trait bounds.
///
/// This function works for any type `T` that implements the `Debug` trait.
fn print_value<T: Debug>(value: T) {
    println!("The value is: {:?}", value); // `{:?}` can be used, because `value` implements `Debug`.
}

/// Type constraints can be written in a separate `where` clause for clarity.
/// You can also combine multiple trait bounds with `+`.
fn print_value2<T>(value: T)
where
    T: Debug + Display,
{
    println!("Compare {} and {:?}", value, value);
}

/// All type parameters have an implicit bound of `Sized`.
/// The special syntax `?Sized` can be used to remove this bound if it is not
/// appropriate.
///
/// This function can work with types that may not have a known size at compile
/// time.
fn generic<T: ?Sized + Display>(t: &T) {
    println!("{}", t);
}

fn main() {
    print_value(1);
    print_value2("2");
    let s = String::from("hello");
    generic(&s[..]);
    generic(&s);
}

Diverging Functions

Functions that are guaranteed never to return (e.g., they loop forever or exit the process) have the special return type ! (the "never" type).

/// This function diverges, meaning it never returns.
/// It uses the `!` (Never) type to indicate this.
fn foo() -> ! {
    panic!("This call never returns.");
}

// This function never returns control.
#[allow(dead_code)]
fn exit_program() -> ! {
    println!("Exiting the process...");
    std::process::exit(1);
}

// This function loops forever.
#[allow(dead_code)]
fn forever() -> ! {
    loop {
        println!("and ever...");
        std::thread::sleep(std::time::Duration::from_secs(1));
    }
}

fn main() {
    foo(); // This will panic.
}

Function Pointers

Functions are first-class citizens in Rust. This means you can treat them like any other value: assign them to variables, pass them as arguments to other functions, etc. The type of a function pointer looks like fn(i32) -> i32.

/// Add one to the input.
fn add_one(x: i32) -> i32 {
    x + 1
}

/// This function takes a function pointer.
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    f(arg) + f(arg)
}

fn main() {
    let func_ptr: fn(i32) -> i32 = add_one; // Assign a function pointer to a variable.
    println!("Calling via the pointer: {}", func_ptr(10)); // Output: 11.

    let answer = do_twice(add_one, 5); // Pass `add_one` as an argument.
    println!("The answer is: {}", answer); // Output: 12 ( (5+1) + (5+1) )
}

Related Topics

  • Closures.
  • Rust Patterns.