2.3Function parameters and arguments

Last updated June 11, 2026

Return values move data out of functions. This lesson opens the inbound lane, which turns functions from fixed routines into machines with dials.

Parameters and arguments

A parameter is a variable listed in a function's definition, between the parentheses, with a required type. An argument is the actual value the caller supplies for it. The classic specimen:

fn add(x: i32, y: i32) -> i32 {
    x + y
}

fn main() {
    println!("{}", add(4, 5));
    println!("{}", add(1 + 2, 3 * 4));
}
9
15

When add(4, 5) runs, the parameters are created like fresh local variables, x initialized with 4 and y with 5, the body runs, and the tail expression delivers the result. The second call shows arguments can be any expressions of the right type: they're evaluated first, then the call happens with the results (so add receives 3 and 12).

Calls nest, too, because a call is just an expression:

println!("{}", add(1, add(2, 3)));

The inner add(2, 3) evaluates to 5, which becomes the outer call's second argument, giving 6.

Why the types are mandatory

With let, the compiler infers types and you mostly omit them. Parameters don't get that option; fn add(x, y) is a syntax error. This is a deliberate design decision, not inference being lazy, and it buys you two things.

Key insight

A function's name, parameters, and return type form its signature: the complete public contract of what goes in and what comes out. Because signatures are explicit, the compiler can check every call site against the contract locally. Pass the wrong thing and the error appears at your call, phrased in terms of the contract, rather than as a confusing failure somewhere inside the function's body. Explicit signatures are also documentation that can't go stale: fn area(width: i32, height: i32) -> i32 tells you nearly everything before you read a single body line.

Here's the contract being enforced, both ways. Wrong number of arguments:

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

Wrong type of argument, and it's our old friend E0308, pointing at the exact argument, with the function's definition shown for reference. In both cases the error lands at the call site, names the gap, and drafts the fix. That's the explicit-signature dividend, paid every time you mistype a call.

Arguments are copied in

One more behavior to nail down now, because it prevents a classic confusion. For the simple types we're using, the argument's value is copied into the parameter. The parameter is the function's own private variable; what happens to it stays in the function:

fn bump(mut n: i32) {
    n = n + 1;
    println!("inside bump: n is {n}");
}

fn main() {
    let total = 5;
    bump(total);
    println!("back in main: total is {total}");
}
inside bump: n is 6
back in main: total is 5

bump received a copy of total's value and incremented the copy. (The mut in mut n: i32 makes the function's own copy assignable; it asks nothing of the caller.) main's variable never flinched.

What you're taking on credit

"Arguments are copied" is the whole truth for integers and the other simple types in these chapters, and a simplification for types like String. The full story is the story of this course: ownership, chapter 8, with chapter 9 covering how functions borrow values instead of consuming them (which finally explains 1.6's &mut). Until then, every example chooses types where "copied" is exactly right.

When a parameter exists to satisfy a shape but goes unused, the underscore convention from chapter 1 applies: name it _ or _reason, and the unused-variable warning stands down. (Rare now; it'll matter when traits arrive in chapter 16.)

Quiz time

Question #1

What does this program print?

fn mystery(a: i32, b: i32) -> i32 {
    a * 10 + b
}

fn main() {
    println!("{}", mystery(3, mystery(2, 1)));
}
Show solution
51

Inner call first: mystery(2, 1) is 21. Then mystery(3, 21) is 30 + 21 = 51.

Question #2

Predict the compiler error:

fn greet(name: i32) {
    println!("Hello, number {name}!");
}

fn main() {
    greet("Ada");
}
Show solution

E0308, mismatched types, at the call site: the argument "Ada" is a &str, but the parameter contract says i32. (The deeper fix is that the parameter's type is wrong for its job; greeting people by i32 has known limitations.)

Question #3

Write a function final_price(price: i32, discount: i32) -> i32 that returns the price minus the discount, and a main that uses it to print Sale price: 80 from arguments 100 and 20.

Show solution
fn final_price(price: i32, discount: i32) -> i32 {
    price - discount
}

fn main() {
    println!("Sale price: {}", final_price(100, 20));
}

Parameters in, return values out: the data plumbing is complete. Next, a closer look at where variables live and die, which is about to matter more than it sounds.