2.1Introduction to functions

Last updated June 11, 2026

Chapter introduction

Chapter 1 ended with a program whose main did everything. That stops scaling almost immediately, and this chapter is about the fix: functions, the unit from which all larger program structure is built. By the chapter's end you'll be designing programs as teams of small functions, which is how all real Rust is written.

Lesson 1.1 defined a function as a named bundle of statements. That was true and incomplete. The better definition: a function is a reusable piece of program, with a name, dedicated to one job, which other code can run as a step of its own work. main is one. The upgrade in this chapter is that you start writing the others.

Defining and calling a function

A function definition looks exactly like main's, because main was never special syntax:

fn print_warning() {
    println!("Careful! The floor is wet.");
}

fn, a snake_case name, parentheses (empty for now; next lessons fill them), and a brace-wrapped body. Definitions can sit before or after main in the file, at the same level (not inside another function's braces, conventionally).

To run it, you call it by name, with parentheses:

fn main() {
    println!("Entering the kitchen.");
    print_warning();
    println!("Walking carefully.");
}

fn print_warning() {
    println!("Careful! The floor is wet.");
}
Entering the kitchen.
Careful! The floor is wet.
Walking carefully.

Follow the control flow, because this trace is the whole lesson: execution starts at main, prints the first line, and hits the call. At that moment main pauses, execution jumps into print_warning, runs its body, and when that body ends, execution returns to main, picking up exactly where it left off. Like putting a bookmark in one book to glance at another, the program never loses its place.

A function can be called as many times as you like; that's the "reusable" earning its keep:

fn main() {
    print_warning();
    print_warning();
}

fn print_warning() {
    println!("Careful! The floor is wet.");
}
Careful! The floor is wet.
Careful! The floor is wet.

And functions call functions. Each call adds a bookmark, each completed body returns to the most recent one:

fn main() {
    println!("main: start");
    do_a();
    println!("main: end");
}

fn do_a() {
    println!("a: start");
    do_b();
    println!("a: end");
}

fn do_b() {
    println!("b: here");
}
main: start
a: start
b: here
a: end
main: end

If you can predict that output cold, you understand function calls. (The machinery that tracks all those bookmarks is called the call stack, and it stars in lesson 3.5 where you'll watch it live.)

Warning

A function is called with parentheses: print_warning(). Write the name without them and you haven't called anything; oddly enough, print_warning; compiles, does nothing at all, and earns only a warning (a "path statement with no effect"). If a function mysteriously never runs, count your parentheses before you question your sanity.

Definition order doesn't matter

Notice that main happily called functions defined below it in the file. Rust reads the whole file before judging any of it, so functions can be defined in whatever order reads best. The convention you'll see everywhere (and that this course uses) puts main first, helpers after, so files read top-down from the big picture to the details.

If you've seen C or C++, you'll recognize what's absent: no forward declarations, no prototypes, no "declared before use" choreography. That entire apparatus has no Rust equivalent at all, one more of the C++ chores lesson 0.3 promised you'd never meet.

For advanced readers

Rust does allow defining a function inside another function's body. It's legal, occasionally tidy for a helper used in exactly one place, and rare in practice; the nested function is just an ordinary function whose name is only visible inside the enclosing one. File it away; everything in this course uses top-level functions.

Quiz time

Question #1

In the three-function trace example above, what would the output be if main's body were the two statements do_b(); then do_a();?

Show solution
b: here
a: start
b: here
a: end

do_b runs first (one line), then do_a runs and calls do_b again as its middle step. Each call is a fresh visit.

Question #2

What's the difference between defining a function and calling it?

Show solution

Defining (fn name() { ... }) creates the function and runs nothing. Calling (name();) runs its body, then returns to the caller. A definition with no calls is dormant code; a call requires a definition to exist somewhere in the file.

Question #3

This program compiles. What happens when it runs, and what does the compiler mutter about?

fn main() {
    greet;
}

fn greet() {
    println!("Hello!");
}
Show solution

It prints nothing. greet; names the function without calling it (no parentheses), a statement with no effect, and the compiler warns exactly that ("path statement with no effect"). The fix is greet();.

So far our functions only do; they can't hand anything back. Return values, next, are where functions start participating in computation.