No history yet

Functions and Modules

Building with Functions

So far, you've written code inside the main function. This is the entry point for every Rust program, but you wouldn't want to stuff all your logic in one place. Just like a good essay is broken into paragraphs, a good program is broken into functions. Functions are named blocks of code that perform a specific task.

function

noun

A reusable block of code that performs a specific action. Functions can take inputs (parameters) and produce an output (return value).

You declare a function in Rust using the fn keyword, followed by the function's name. Let's create a simple function that greets a user and call it from main.

fn main() {
    println!("Hello from main!");
    say_hello(); // Calling our new function
}

fn say_hello() {
    println!("Hello from the say_hello function!");
}

When you run this, you'll see both messages printed. The program starts in main, calls say_hello, executes the code inside it, and then returns to main to finish.

Passing Data to Functions

Functions become much more powerful when you can pass information to them. These inputs are called parameters. You must declare the type of each parameter, just like you do for variables.

fn main() {
    print_sum(5, 6);
}

// This function takes two 32-bit integers as parameters
fn print_sum(x: i32, y: i32) {
    println!("The sum is: {}", x + y);
}

Functions can also send data back. This is called a return value. You declare the return type after an arrow ->. In Rust, the last expression in a function is automatically returned. You don't need a return keyword, and you don't add a semicolon to the line.

This might feel strange at first, but it's a common pattern in expression-based languages like Rust. Omitting the semicolon signals that the value of the expression should be returned.

fn main() {
    let result = add_five(10);
    println!("The result is {}", result);
}

// This function takes one i32 and returns an i32
fn add_five(num: i32) -> i32 {
    num + 5 // No semicolon means this value is returned
}

The add_five function takes 10, adds 5 to it, and the expression num + 5 evaluates to 15. This value is returned and bound to the result variable in main.

Organizing Code with Modules

As your program grows, you'll want to organize your code into logical units. In Rust, the primary way to do this is with modules. Think of a module as a namespace, a container for grouping related functions, structs, and even other modules.

module

noun

A namespace in Rust that contains definitions of functions, structs, traits, and other items, used to organize code and control privacy.

You create a module with the mod keyword. By default, everything inside a module is private. This means it can only be accessed by code within that same module. To make an item, like a function, accessible from outside the module, you use the pub keyword, which stands for public.

mod greetings {
    // This function is public and can be called from outside.
    pub fn english() {
        println!("Hello!");
    }

    // This function is private to the 'greetings' module.
    fn spanish() {
        println!("Hola!");
    }
}

fn main() {
    // We use the '::' path separator to access the function
    greetings::english();

    // The following line would cause a compile error:
    // greetings::spanish(); // Error: function `spanish` is private
}

Modules can also be nested, allowing you to create a clear hierarchy for complex projects.

For example, a game might have a player module, and inside that, you could have an inventory module. You'd access a function in it with player::inventory::some_function().

This system of modules and public/private visibility helps you build maintainable code. You can expose a clean public API while keeping the internal implementation details hidden. This prevents other parts of your code from depending on details that might change later.

Time to check your understanding.

Quiz Questions 1/5

How do you declare a function named calculate_score in Rust?

Quiz Questions 2/5

Which function signature correctly defines a function that accepts a 32-bit signed integer and returns a boolean?

By combining functions and modules, you can build well-structured, readable, and reusable code, which is essential for any project that grows beyond a few lines.