Functions

Function Parameters

A parameter is a variable declared in a function's signature that receives a value when the function is called. The value supplied by the caller is called an argument. Parameters appear in the definition, arguments appear in the call.

fn describe(name: &str, age: u32) {
    //       ^^^^^^^^^^  ^^^^^^^^ parameters
    println!("{name} is {age}");
}

fn main() {
    describe("Ada", 36);
    //       ^^^^^^^^^ arguments
}
Ada is 36

Declaring Parameters

Each parameter is written as name: Type, and parameters are separated by commas. A trailing comma after the last parameter is allowed.

fn describe(name: &str, age: u32, height_m: f64) {
    println!("{name} is {age} years old and {height_m} m tall");
}

fn main() {
    describe("Ada", 36, 1.68);
    describe("Grace", 85, 1.62);
}
Ada is 36 years old and 1.68 m tall
Grace is 85 years old and 1.62 m tall

The type is required on every parameter. The compiler does not infer parameter types, so the signature alone states what a caller must provide.

A long parameter list can be split across lines, one parameter per line. This is the layout cargo fmt produces when the signature does not fit on one line.

fn describe(
    name: &str,
    age: u32,
) {
    println!("{name}: {age}");
}

fn main() {
    describe(
        "Ada",
        36,
    );
}
Ada: 36

Arguments Are Matched by Position

Rust has no named arguments and no default values. The first argument goes to the first parameter, the second to the second, and so on. The count and the types must match the signature exactly.

// ❌ does not compile
fn describe(name: &str, age: u32) {
    println!("{name} is {age}");
}

fn main() {
    describe(30, "Ada");
}
error[E0308]: arguments to this function are incorrect
 --> main.rs:7:5
  |
7 |     describe(30, "Ada");
  |     ^^^^^^^^ --  ----- expected `u32`, found `&'static str`
  |              |
  |              expected `&str`, found `{integer}`
  |
note: function defined here
 --> main.rs:2:4
  |
2 | fn describe(name: &str, age: u32) {
  |    ^^^^^^^^
help: swap these arguments
  |
7 -     describe(30, "Ada");
7 +     describe("Ada", 30);
  |

error: aborting due to 1 previous error

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

When two parameters have the same type, the compiler cannot detect swapped arguments. fn transfer(from: u32, to: u32) accepts transfer(2, 1) as readily as transfer(1, 2). Distinct types, such as a wrapper struct per kind of ID, make these mistakes compile errors.

Alternatives to Default Arguments

An optional argument is expressed with Option<T>. A group of settings, most of which usually keep their defaults, is expressed with a struct that implements Default.

fn greet(name: &str, greeting: Option<&str>) {
    let greeting = greeting.unwrap_or("Hello");
    println!("{greeting}, {name}!");
}

struct WindowOptions {
    width: u32,
    height: u32,
    title: &'static str,
}

impl Default for WindowOptions {
    fn default() -> Self {
        WindowOptions {
            width: 800,
            height: 600,
            title: "Untitled",
        }
    }
}

fn open_window(options: WindowOptions) {
    println!("{} ({}x{})", options.title, options.width, options.height);
}

fn main() {
    greet("Ada", None);
    greet("Ada", Some("Welcome"));
    open_window(WindowOptions::default());
    open_window(WindowOptions {
        title: "Editor",
        ..Default::default()
    });
}
Hello, Ada!
Welcome, Ada!
Untitled (800x600)
Editor (800x600)

Arguments Are Expressions

An argument can be any expression: a variable, a literal, an arithmetic expression, another function call, or a block. Arguments are evaluated from left to right, before the function body starts.

fn square(n: i32) -> i32 {
    n * n
}

fn main() {
    let x = 5;
    println!("{}", square(x));
    println!("{}", square(x + 1));
    println!("{}", square(square(2)));
    println!(
        "{}",
        square({
            let base = 3;
            base + 1
        })
    );
}
25
36
16
16

The evaluation order is visible when the arguments have side effects:

fn trace(label: &str, value: i32) -> i32 {
    println!("evaluating {label}");
    value
}

fn add(a: i32, b: i32) -> i32 {
    println!("inside add");
    a + b
}

fn main() {
    let result = add(trace("first", 1), trace("second", 2));
    println!("{result}");
}
evaluating first
evaluating second
inside add
3

Passing by Value: Copy and Move

By default an argument is passed by value: the value is transferred into the parameter. What that means depends on the type.

  • A type that implements Copy (integers, floats, bool, char, and tuples and arrays of Copy types) is duplicated. The caller keeps its own copy.
  • Any other type is moved. The parameter becomes the new owner, and the caller can no longer use the variable.

Copy Types

A parameter is a new variable that holds its own copy. Changing it does not affect the caller. A parameter is immutable unless it is declared with mut:

fn bump(mut n: i32) -> i32 {
    n += 1;
    n
}

fn main() {
    let value = 10;
    let result = bump(value);
    println!("value = {value}, result = {result}");
}
value = 10, result = 11

mut in front of a parameter name makes the local variable mutable. It is not part of the function's type and does not let the function change the caller's variable. Without mut, assigning to a parameter is an error:

// ❌ does not compile
fn bump(n: i32) -> i32 {
    n += 1;
    n
}

fn main() {
    println!("{}", bump(1));
}
error[E0384]: cannot assign to immutable argument `n`
 --> main.rs:3:5
  |
3 |     n += 1;
  |     ^^^^^^ cannot assign to immutable argument
  |
help: consider making this binding mutable
  |
2 | fn bump(mut n: i32) -> i32 {
  |         +++

error: aborting due to 1 previous error

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

Move Types

A String, Vec<T>, or Box<T> owns heap memory. Passing it by value moves ownership into the function:

Before the call                 After consume(message)

main                            main
+------------+                  +------------+
| message    |                  | message    |  (moved, unusable)
|  ptr ------+--> "hello"       +------------+
|  len = 5   |
|  cap = 5   |                  consume
+------------+                  +------------+
                                | text       |
                                |  ptr ------+--> "hello"
                                |  len = 5   |
                                |  cap = 5   |
                                +------------+

The string is freed when text goes out of scope at the end of consume. Using message afterwards is a compile error:

// ❌ does not compile
fn consume(text: String) {
    println!("consumed: {text}");
}

fn main() {
    let message = String::from("hello");
    consume(message);
    println!("{message}");
}
error[E0382]: borrow of moved value: `message`
 --> main.rs:8:16
  |
6 |     let message = String::from("hello");
  |         ------- move occurs because `message` has type `String`, which does not implement the `Copy` trait
7 |     consume(message);
  |             ------- value moved here
8 |     println!("{message}");
  |                ^^^^^^^ value borrowed here after move
  |
note: consider changing this parameter type in function `consume` to borrow instead if owning the value isn't necessary
 --> main.rs:1:18
  |
1 | fn consume(text: String) {
  |    -------       ^^^^^^ this parameter takes ownership of the value
  |    |
  |    in this function
help: consider cloning the value if the performance cost is acceptable
  |
7 |     consume(message.clone());
  |                    ++++++++

error: aborting due to 1 previous error

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

There are three ways to keep using a value after passing it to a function:

  1. Borrow it with a reference, &message, as described in the next section.
  2. Pass a copy with message.clone().
  3. Have the function give the value back as its return value:
fn show_vec(v: Vec<i32>) -> Vec<i32> {
    println!("{v:?}");
    v
}

fn main() {
    let numbers = vec![1, 2, 3];
    let numbers = show_vec(numbers);
    println!("{}", numbers.len());
}
[1, 2, 3]
3

Taking ownership is the right choice when the function needs to keep, store, or consume the value. Otherwise a reference is lighter.

Passing by Reference

A parameter of type &T is a shared reference: the function can read the value but not change it, and the caller keeps ownership. A parameter of type &mut T is a mutable reference: the function can change the value in place.

The caller writes & or &mut at the call site, so borrowing is visible in the call.

fn length(text: &str) -> usize {
    text.len()
}

fn append_exclamation(text: &mut String) {
    text.push('!');
}

fn main() {
    let mut message = String::from("hello");
    println!("length = {}", length(&message));
    append_exclamation(&mut message);
    println!("{message}");
}
length = 5
hello!
Parameter type Caller writes Function can Caller afterwards
T value Own, consume, or mutate (with mut) its own copy Loses the value unless T: Copy
&T &value Read Still owns the value
&mut T &mut value Read and modify Still owns the value, with changes applied

Mutability is checked at both ends. The function must ask for &mut, and the caller must pass &mut of a variable that was declared mut.

// ❌ does not compile
fn append_exclamation(text: &mut String) {
    text.push('!');
}

fn main() {
    let mut message = String::from("hello");
    append_exclamation(&message);
    println!("{message}");
}
error[E0308]: mismatched types
 --> main.rs:8:24
  |
8 |     append_exclamation(&message);
  |     ------------------ ^^^^^^^^ types differ in mutability
  |     |
  |     arguments to this function are incorrect
  |
  = note: expected mutable reference `&mut String`
                     found reference `&String`
note: function defined here
 --> main.rs:2:4
  |
2 | fn append_exclamation(text: &mut String) {
  |    ^^^^^^^^^^^^^^^^^^ -----------------

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0308`.
// ❌ does not compile
fn append_exclamation(text: &mut String) {
    text.push('!');
}

fn main() {
    let message = String::from("hello");
    append_exclamation(&mut message);
    println!("{message}");
}
error[E0596]: cannot borrow `message` as mutable, as it is not declared as mutable
 --> main.rs:8:24
  |
8 |     append_exclamation(&mut message);
  |                        ^^^^^^^^^^^^ cannot borrow as mutable
  |
help: consider changing this to be mutable
  |
7 |     let mut message = String::from("hello");
  |         +++

error: aborting due to 1 previous error

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

Preferring &str and &[T]

A function that only reads a string should take &str, not &String. A function that only reads a sequence should take &[T], not &Vec<T>. Rust automatically converts &String to &str and &Vec<T> to &[T] at a call site (deref coercion), so one signature accepts all of the common types.

fn total(numbers: &[i32]) -> i32 {
    numbers.iter().sum()
}

fn shout(text: &str) -> String {
    text.to_uppercase()
}

fn main() {
    let array = [1, 2, 3];
    let vector = vec![4, 5, 6];
    println!("{}", total(&array));
    println!("{}", total(&vector));
    println!("{}", total(&vector[1..]));

    let owned = String::from("rust");
    println!("{}", shout(&owned));
    println!("{}", shout("literal"));
}
6
15
11
RUST
LITERAL

total accepts an array, a Vec, and part of a Vec. shout accepts a String and a string literal.

Unsized Types Cannot Be Parameters

A parameter is stored in the function's stack frame, so its size must be known at compile time. str and [T] have no fixed size, so they can only be used behind a pointer such as &str or &[T].

// ❌ does not compile
fn takes_str(text: str) {}

fn main() {
    takes_str(*"abc");
}
error[E0277]: the size for values of type `str` cannot be known at compilation time
 --> main.rs:2:20
  |
2 | fn takes_str(text: str) {}
  |                    ^^^ doesn't have a size known at compile-time
  |
  = help: the trait `Sized` is not implemented for `str`
help: function arguments must have a statically known size, borrowed types always have a known size
  |
2 | fn takes_str(text: &str) {}
  |                    +

error[E0277]: the size for values of type `str` cannot be known at compilation time
 --> main.rs:5:15
  |
5 |     takes_str(*"abc");
  |               ^^^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `Sized` is not implemented for `str`
  = note: all function arguments must have a statically known size

error: aborting due to 2 previous errors

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

Arrays as Parameters

An array's length is part of its type. [i32; 3] and [i32; 4] are different types, and an array passed by value is copied when its elements are Copy.

fn first_element(values: [i32; 3]) -> i32 {
    values[0]
}

fn main() {
    let values = [10, 20, 30];
    println!("{}", first_element(values));
    println!("{:?}", values);
}
10
[10, 20, 30]

An array of another length is a type mismatch:

// ❌ does not compile
fn first_element(values: [i32; 3]) -> i32 {
    values[0]
}

fn main() {
    let values = [10, 20, 30, 40];
    println!("{}", first_element(values));
}
error[E0308]: mismatched types
 --> main.rs:8:34
  |
8 |     println!("{}", first_element(values));
  |                    ------------- ^^^^^^ expected an array with a size of 3, found one with a size of 4
  |                    |
  |                    arguments to this function are incorrect
  |
note: function defined here
 --> main.rs:2:4
  |
2 | fn first_element(values: [i32; 3]) -> i32 {
  |    ^^^^^^^^^^^^^ ----------------

error: aborting due to 1 previous error

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

A slice parameter, &[i32], accepts arrays of any length. Const generics ([T; N]) are another option.

Patterns in Parameters

The part before the colon is a pattern, not only a name. Tuples and structs can be destructured directly in the signature, and _ ignores a parameter.

fn distance((x1, y1): (f64, f64), (x2, y2): (f64, f64)) -> f64 {
    ((x2 - x1).powi(2) + (y2 - y1).powi(2)).sqrt()
}

struct Point {
    x: i32,
    y: i32,
}

fn show(Point { x, y }: Point) {
    println!("({x}, {y})");
}

fn ignore_second(value: i32, _: i32) -> i32 {
    value
}

fn main() {
    println!("{}", distance((0.0, 0.0), (3.0, 4.0)));
    show(Point { x: 1, y: -2 });
    println!("{}", ignore_second(7, 8));
}
5
(1, -2)
7

Only irrefutable patterns, those that always match, are allowed. A pattern such as Some(x) is not accepted, because the argument might be None.

Unused Parameters

A parameter that the body never uses produces a warning. Prefix the name with an underscore to mark it as intentional.

fn area(width: u32, height: u32, depth: u32) -> u32 {
    width * height
}

fn main() {
    println!("{}", area(2, 3, 4));
}
warning: unused variable: `depth`
 --> main.rs:1:34
  |
1 | fn area(width: u32, height: u32, depth: u32) -> u32 {
  |                                  ^^^^^ help: if this is intentional, prefix it with an underscore: `_depth`
  |
  = note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default

warning: 1 warning emitted

6

Generic and impl Trait Parameters

A parameter type can be a generic type parameter or impl Trait, so that one function accepts any type with the required capabilities. The compiler generates a specialised version of the function for each concrete type it is called with.

use std::fmt::Display;

fn print_twice(value: impl Display) {
    println!("{value} {value}");
}

fn largest<T: PartialOrd + Copy>(a: T, b: T) -> T {
    if a > b { a } else { b }
}

fn main() {
    print_twice(5);
    print_twice("hi");
    println!("{}", largest(3, 9));
    println!("{}", largest(2.5, 1.5));
}
5 5
hi hi
9
2.5

In largest, both arguments must be the same type T. largest(1, 2.5) is rejected.

Closures and function pointers can also be passed as parameters. Those are covered in Function Pointers and Closures - Introduction.