Functions

Defining Functions

A function is a named, reusable block of code. It is defined once with the fn keyword and run any number of times by calling it by name. Every Rust program starts in a function, main, and is built out of other functions that main calls.

Syntax

fn name(parameter: Type, parameter: Type) -> ReturnType {
    body
}
Part Required Meaning
fn yes Keyword that starts a function definition
name yes Identifier used to call the function
( ... ) yes Parameter list. The parentheses are required even when it is empty
-> Type no Return type. When omitted, the function returns (), the unit type
{ ... } yes Body, a block of statements with an optional final expression

The smallest possible function has no parameters and no return type:

fn main() {
    println!("main starts");
    greet();
    greet();
    println!("main ends");
}

fn greet() {
    println!("Hello from greet");
}
main starts
Hello from greet
Hello from greet
main ends

greet() is a call expression. Execution jumps into greet, runs its body, and then returns to the line after the call. The function is written once and runs twice.

A function with parameters and a return value:

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

fn main() {
    let total = add(2, 3);
    println!("2 + 3 = {total}");
    println!("add(10, 20) = {}", add(10, 20));
}
2 + 3 = 5
add(10, 20) = 30

The body of add ends with a + b and no semicolon. That final expression is the value the function returns. Parameters are covered in Function Parameters, and return values in Return Types and Returning Expressions.

The main Function

main is the entry point of a binary crate. The operating system starts the program, the Rust runtime does some setup, and then it calls main. When main returns, the program exits.

fn main() {
    println!("This is where execution begins.");
}
This is where execution begins.

main takes no parameters. Command-line arguments are read through std::env::args() instead. A library crate has no main.

Naming Functions

Function names use snake case: lowercase words separated by underscores.

Examples: calculate_total_price, is_even, parse_header.

This is a convention enforced by a compiler lint, non_snake_case. A different style still compiles, but it produces a warning:

fn PrintValue() {
    println!("value");
}

fn main() {
    PrintValue();
}
warning: function `PrintValue` should have a snake case name
 --> main.rs:1:4
  |
1 | fn PrintValue() {
  |    ^^^^^^^^^^ help: convert the identifier to snake case: `print_value`
  |
  = note: `#[warn(non_snake_case)]` (part of `#[warn(nonstandard_style)]`) on by default

warning: 1 warning emitted

value

Name rules:

  • A name may contain letters, digits, and underscores, and may not start with a digit.
  • A name cannot be a keyword such as fn, match, or type. A keyword can be used as a name with the raw identifier prefix r#, as in r#match, but this is rarely a good idea.
  • A name starting with an underscore, such as _helper, tells the compiler that an unused function is intentional and silences the unused warning.
  • A good function name is a verb or a verb phrase that describes what the function does: read_config, send_message. Functions that return a bool are usually named as a question: is_empty, has_key.

Definition Order Does Not Matter

A function can be called before the line where it is defined. Functions are items, and the compiler collects all items in a module before it checks the bodies of any function.

fn main() {
    println!("{}", double(21));
}

fn double(n: i32) -> i32 {
    n * 2
}
42

This differs from let bindings inside a function, which must appear before they are used.

Each Name Has One Definition

Rust has no function overloading. Two functions with the same name in the same module are an error, even when their parameter types differ:

// ❌ does not compile
fn describe(n: i32) {
    println!("int {n}");
}

fn describe(n: f64) {
    println!("float {n}");
}

fn main() {
    describe(1);
    describe(1.5);
}
error[E0428]: the name `describe` is defined multiple times
 --> main.rs:6:1
  |
2 | fn describe(n: i32) {
  | ------------------- previous definition of the value `describe` here
...
6 | fn describe(n: f64) {
  | ^^^^^^^^^^^^^^^^^^^ `describe` redefined here
  |
  = note: `describe` must be defined only once in the value namespace of this module

error[E0308]: mismatched types
  --> main.rs:12:14
   |
12 |     describe(1.5);
   |     -------- ^^^ expected `i32`, found floating-point number
   |     |
   |     arguments to this function are incorrect
   |
note: function defined here
  --> main.rs:2:4
   |
 2 | fn describe(n: i32) {
   |    ^^^^^^^^ ------

error: aborting due to 2 previous errors

Some errors have detailed explanations: E0308, E0428.
For more information about this error, try `rustc --explain E0308`.

Giving one name to behavior that differs by type is done with traits and generics. Rust also has no default argument values, no named arguments, and no variadic functions (a variable number of arguments) for ordinary functions. Each call must supply exactly the declared arguments, in order.

Calling Functions

A call is the function name followed by parentheses that contain one argument per parameter. The compiler checks the count and the types of the arguments.

// ❌ does not compile
fn double(n: i32) -> i32 {
    n * 2
}

fn main() {
    println!("{}", double(4, 5));
    println!("{}", double());
}
error[E0061]: this function takes 1 argument but 2 arguments were supplied
 --> main.rs:7:20
  |
7 |     println!("{}", double(4, 5));
  |                    ^^^^^^    - unexpected argument #2 of type `{integer}`
  |
note: function defined here
 --> main.rs:2:4
  |
2 | fn double(n: i32) -> i32 {
  |    ^^^^^^
help: remove the extra argument
  |
7 -     println!("{}", double(4, 5));
7 +     println!("{}", double(4));
  |

error[E0061]: this function takes 1 argument but 0 arguments were supplied
 --> main.rs:8:20
  |
8 |     println!("{}", double());
  |                    ^^^^^^-- argument #1 of type `i32` is missing
  |
note: function defined here
 --> main.rs:2:4
  |
2 | fn double(n: i32) -> i32 {
  |    ^^^^^^ ------
help: provide the argument
  |
8 |     println!("{}", double(/* i32 */));
  |                           +++++++++

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0061`.

Calling a name that does not exist is a different error:

// ❌ does not compile
fn main() {
    say_hello();
}
error[E0425]: cannot find function `say_hello` in this scope
 --> main.rs:3:5
  |
3 |     say_hello();
  |     ^^^^^^^^^ not found in this scope

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0425`.

Functions Are Items

A function definition is an item. It is not an expression or a statement that runs. It does not execute where it is written. It only declares the function. A free function must have a body, and the definition is not followed by a semicolon.

// ❌ does not compile
fn square(n: i32) -> i32;

fn main() {
    println!("{}", square(3));
}
error: free function without a body
 --> main.rs:2:1
  |
2 | fn square(n: i32) -> i32;
  | ^^^^^^^^^^^^^^^^^^^^^^^^-
  |                         |
  |                         help: provide a definition for the function: `{ <body> }`

error: aborting due to 1 previous error

At the top level of a file, only items are allowed: functions, structs, enums, constants, statics, modules, use declarations, and similar. Statements such as let are only valid inside a function body.

// ❌ does not compile
let answer = 42;

fn main() {
    println!("{answer}");
}
error: expected item, found keyword `let`
 --> main.rs:2:1
  |
2 | let answer = 42;
  | ^^^
  | |
  | `let` cannot be used for global variables
  | help: consider using `static` or `const` instead of `let`
  |
  = note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>

error: aborting due to 1 previous error

Parameters Need Types

Every parameter must have a declared type. Rust does not infer parameter types from the function body or from the call sites.

// ❌ does not compile
fn square(n) -> i32 {
    n * n
}

fn main() {
    println!("{}", square(3));
}
error: expected one of `:`, `@`, or `|`, found `)`
 --> main.rs:2:12
  |
2 | fn square(n) -> i32 {
  |            ^ expected one of `:`, `@`, or `|`
  |
help: if this is a parameter name, give it a type
  |
2 | fn square(n: TypeName) -> i32 {
  |            ++++++++++
help: if this is a type, explicitly ignore the parameter name
  |
2 | fn square(_: n) -> i32 {
  |           ++

error: aborting due to 1 previous error

This rule makes a function's signature a complete contract. A reader and the compiler can understand how to call a function from its first line, without reading the body.

Visibility

Functions are private to the module that defines them by default. The pub keyword makes a function visible outside its module.

mod shapes {
    pub fn square_area(side: f64) -> f64 {
        side * side
    }

    fn secret() -> i32 {
        42
    }

    pub fn reveal() -> i32 {
        secret()
    }
}

fn main() {
    println!("{}", shapes::square_area(3.0));
    println!("{}", shapes::reveal());
}
9
42

shapes::secret() can only be called from inside shapes. Calling it from outside is an error:

// ❌ does not compile
mod shapes {
    fn secret() -> i32 {
        42
    }
}

fn main() {
    println!("{}", shapes::secret());
}
error[E0603]: function `secret` is private
 --> main.rs:9:28
  |
9 |     println!("{}", shapes::secret());
  |                            ^^^^^^ private function
  |
note: the function `secret` is defined here
 --> main.rs:3:5
  |
3 |     fn secret() -> i32 {
  |     ^^^^^^^^^^^^^^^^^^

error: aborting due to 1 previous error

main does not need pub. The compiler treats it as the program's entry point.

Qualifiers

Keywords can appear before fn to change what kind of function is defined. Several can be combined in a fixed order: pub const async unsafe extern "C" fn.

Qualifier Effect
pub The function is visible outside its module
const The function can be called at compile time, for example to initialise a const
async The function returns a future instead of running immediately
unsafe The caller must use an unsafe block, because the function has requirements the compiler cannot check
extern "C" The function uses the C calling convention, so it can be called from or to C code

A const fn can be called in a normal program as well as in a constant:

const fn max_of(a: u32, b: u32) -> u32 {
    if a > b { a } else { b }
}

const LIMIT: u32 = max_of(10, 25);

fn main() {
    println!("{LIMIT}");
    println!("{}", max_of(3, 9));
}
25
9

LIMIT is computed by the compiler. The call max_of(10, 25) does not run when the program starts. The body of a const fn is limited to operations the compiler can evaluate at compile time.

Documenting a Function

A comment that starts with /// immediately before a function is a doc comment. rustdoc turns it into HTML documentation, and editors show it on hover. Doc comments use Markdown.

/// Returns the larger of two numbers.
///
/// If the numbers are equal, either one is returned.
fn larger(a: i32, b: i32) -> i32 {
    if a > b { a } else { b }
}

fn main() {
    println!("{}", larger(7, 12));
}
12

A regular // comment is ignored by rustdoc. Use /// for what a function does and how to call it, and // for notes about how the body works.