Functions in Rust
Learn how to define functions in Rust: parameters, return values, expression-based returns, recursion, scope, closures, and higher-order functions with Docker-ready examples
Functions are the building blocks of every Rust program—you have already met main, the special entry point. Beyond main, functions let you name a piece of behavior, give it typed inputs and a typed output, and reuse it anywhere. Rust takes function signatures seriously: every parameter must have an explicit type, and a non-() return type must be declared with ->. The compiler uses these annotations to guarantee correctness before your program ever runs.
What makes functions in Rust distinctive is that the language is expression-based. The body of a function is a block, and a block evaluates to its final expression. That means you usually return a value simply by writing it as the last line without a semicolon—no return keyword required. This blends the imperative style familiar from C with the value-oriented style of functional languages like OCaml and Haskell, both of which influenced Rust.
Rust is also multi-paradigm in its function support. Functions are first-class values: you can pass a function by name, store one in a variable, and accept one as a parameter. On top of that, closures—anonymous functions that capture their surrounding environment—make higher-order programming natural and ergonomic.
In this tutorial you will define functions with parameters and return values, use both implicit and explicit returns, write a recursive function, reason about variable scope, and finish with closures and higher-order functions.
Defining and Calling Functions
Functions are declared with the fn keyword. Parameters always have explicit types, and the return type follows ->. The key idea to internalize: the last expression in the body is the return value when it has no trailing semicolon.
Create a file named functions.rs:
| |
A few things worth noticing:
- Order does not matter.
maincallsfactorial, which is defined above it, but Rust does not care about definition order—functions are visible throughout the module. - Expression vs. statement. In
add,a + bis an expression returned implicitly. Inabsolute,return -n;is an early-exit statement, while the trailingnis the implicit return for the non-negative case. - Scope.
sumandlocallive only insidemain. Theconst PLANETis declared at module level, so it is reachable from any function.
Closures and Higher-Order Functions
Functions in Rust are values. You can pass a function by name using a function-pointer type (fn(i32) -> i32), and you can write closures—anonymous functions that capture variables from the scope where they are defined. A function that takes or returns another function is called a higher-order function.
Create a file named closures.rs:
| |
Key ideas in this example:
- Closures capture context.
scalecapturesfactorautomatically; a plainfncannot do that. - Functions are first-class.
doubleis passed toapply_twiceby name, just like any other value. - Coercion. A closure that captures nothing—like
|x| x + 1—can be used wherever afnpointer is expected. - Returning closures.
make_adderreturnsimpl Fn(i32) -> i32, andmovemoves the capturedninto the returned closure so it stays valid after the function returns.
Running with Docker
You do not need Rust installed locally—the official image compiles and runs both files. Each .rs file is compiled with rustc into a binary named after the file, then executed.
| |
Expected Output
Running functions.rs:
Welcome to Rust functions!
3 + 4 = 7
absolute(-5) = 5
absolute(8) = 8
5! = 120
local = 14, planet = Earth
Running closures.rs:
scale(4) = 40
apply_twice(double, 5) = 20
apply_twice(|x| x + 1, 5) = 7
add_five(10) = 15
Key Concepts
fndeclares a function, and every parameter must have an explicit type—Rust never infers parameter types.- Return types use
->. A function with no->returns the unit type(). - The last expression is the return value. Omit the semicolon to return it implicitly; use
returnonly for early exits. - Semicolons change meaning.
a + breturns a value;a + b;discards it and evaluates to()—a common beginner pitfall. - Scope is block-based. Variables bound with
letare local to their block;constitems are visible across the module. - Recursion is fully supported, as shown by
factorial, though iterators are often idiomatic for collection processing. - Functions are first-class values that can be passed by name using
fnpointer types. - Closures capture their environment, enabling higher-order programming; use
moveto transfer ownership of captured values into the closure.
Comments
Loading comments...
Leave a Comment