Nested Functions
A nested function (or inner function) is a fn item defined inside the body of another function (or inside any block { ... }). Just like a nested helper struct or constant, a nested function is scoped strictly to the block in which it is declared, keeping helper logic private to the only function that uses it.
Basic Syntax and Scoping
You can place a fn definition anywhere inside another function's body—at the top, middle, or bottom of the block:
fn format_receipt(items: &[(&str, u32)]) -> String {
// Nested helper function visible ONLY inside `format_receipt`:
fn format_cents(cents: u32) -> String {
let dollars = cents / 100;
let remainder = cents % 100;
format!("${dollars}.{remainder:02}")
}
let mut output = String::new();
let mut total_cents = 0;
for &(name, price_cents) in items {
output.push_str(&format!("{name:<12} {}\n", format_cents(price_cents)));
total_cents += price_cents;
}
output.push_str(&format!("Total: {}", format_cents(total_cents)));
output
}
fn main() {
let cart = [("Coffee", 350), ("Croissant", 425), ("Apple", 120)];
println!("{}", format_receipt(&cart));
}
Coffee $3.50
Croissant $4.25
Apple $1.20
Total: $8.95
Inside main(), format_cents does not exist and cannot be called (error[E0425]: cannot find function 'format_cents' in this scope).
Order-Independence Inside the Enclosing Block
Unlike let bindings (including closures assigned with let f = |...| ...;), which are only in scope after the line where they are declared, a nested fn is an item. Items are visible throughout the entire block in which they are defined, even on lines above their definition, and can call themselves recursively:
fn sum_digits(number: u32) -> u32 {
// Called BEFORE its definition below:
helper(number)
}
fn demo() {
let result = call_below(5);
println!("Result from nested fn defined below call: {result}");
fn call_below(x: i32) -> i32 {
x * 10
}
}
fn helper(n: u32) -> u32 {
if n < 10 { n } else { (n % 10) + helper(n / 10) }
}
fn main() {
println!("Sum of digits in 9482: {}", sum_digits(9482));
demo();
}
Sum of digits in 9482: 23
Result from nested fn defined below call: 50
Crucial Rule: Nested Functions Cannot Capture Outer Local Variables
Unlike Python (def), JavaScript (function), or Rust closures (|x| ...), a nested fn in Rust cannot access local variables or parameters of the outer function!
fn outer(multiplier: i32) {
let offset = 10;
fn inner(x: i32) -> i32 {
// ❌ `multiplier` and `offset` are NOT accessible here!
// A `fn` item has a plain code pointer with no hidden environment struct.
}
}
Attempting to reference a local variable or parameter from the enclosing function inside a nested fn fails with E0434:
// ❌ does not compile
fn scale_all(values: &[i32], factor: i32) -> Vec<i32> {
fn multiply(x: i32) -> i32 {
x * factor
}
values.iter().map(|&v| multiply(v)).collect()
}
fn main() {}
error[E0434]: can't capture dynamic environment in a fn item
--> src/main.rs:3:13
|
3 | x * factor
| ^^^^^^
|
= help: use the `|| { ... }` closure form instead
error: aborting due to 1 previous error
Why Nested fn Items Do Not Capture Environment
A fn item compiles to a plain machine-code function with a standard function pointer (fn(i32) -> i32). If it captured outer stack variables, it would require a hidden environment struct passed as an extra argument, which is what a closure (|x| x * factor) does.
To fix E0434, either:
- Pass the outer variable explicitly as a parameter to the nested function (
fn multiply(x: i32, factor: i32) -> i32), or - Use a closure (
let multiply = |x: i32| x * factor;).
fn scale_all(values: &[i32], factor: i32) -> Vec<i32> {
// Option 1: Pass `factor` explicitly to the nested `fn`
fn multiply(x: i32, factor: i32) -> i32 {
x * factor
}
// Option 2: Or use a closure that captures `factor` from `scale_all`
let multiply_closure = |x: i32| x * factor;
let mut out = Vec::new();
for &v in values {
assert_eq!(multiply(v, factor), multiply_closure(v));
out.push(multiply(v, factor));
}
out
}
fn main() {
println!("Scaled: {:?}", scale_all(&[2, 5, 9], 3));
}
Scaled: [6, 15, 27]
Nested fn vs Closure Comparison
| Feature | Nested Function (fn name(...)) |
Closure (let name = |...| ...;) |
|---|---|---|
| Captures outer local variables? | No (E0434—only const, static, and types are accessible) |
Yes (borrows or moves outer variables automatically) |
| Type annotations required? | Yes (parameters and return type must be annotated) | No (inferred from usage) |
Supports recursion (name())? |
Yes (natively supports direct and mutual recursion) | No (a closure cannot easily refer to its own let binding while being defined) |
Supports generic parameters (fn f<T>)? |
Yes (fn helper<T: Display>(x: T)) |
No (closures cannot declare <T> type parameters) |
Can be const fn? |
Yes | No |
| Can be called above its definition? | Yes (hoisted across the entire block) | No (must be declared with let before use) |
Recursive Wrapper Pattern with a Nested Function
Nested functions are particularly idiomatic when a public recursive algorithm needs an extra accumulator or depth parameter that callers should not have to pass manually:
fn binary_search(sorted: &[i32], target: i32) -> Option<usize> {
// Inner recursive helper with explicit `low` and `high` bounds
fn search_range(slice: &[i32], target: i32, low: usize, high: usize) -> Option<usize> {
if low >= high {
return None;
}
let mid = low + (high - low) / 2;
if slice[mid] == target {
Some(mid)
} else if slice[mid] < target {
search_range(slice, target, mid + 1, high)
} else {
search_range(slice, target, low, mid)
}
}
search_range(sorted, target, 0, sorted.len())
}
fn main() {
let data = [3, 8, 15, 23, 42, 56];
println!("Index of 23: {:?}", binary_search(&data, 23));
println!("Index of 10: {:?}", binary_search(&data, 10));
}
Index of 23: Some(3)
Index of 10: None