🎓 A1Learner
🔍

Rust Returning Closures

A returned closure is a closure that a function hands back to its caller. The caller keeps the closure and runs it later.

Return a closure as impl Fn

A closure has a hidden type with no name you can write, so the function promises a kind instead. move makes the returned closure own everything it captures:

fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
    move |x| x + n
}
let add5 = make_adder(5);
println!("{}", add5(10));

An FnMut return lives in a mut variable

Calling this closure changes what it captured, so the variable that stores it must allow change:

fn make_shouter() -> impl FnMut() -> String {
    let mut bangs = String::new();
    move || {
        bangs.push('!');
        format!("go{bangs}")
    }
}
let mut shout = make_shouter();
println!("{} {}", shout(), shout());

An FnOnce return runs exactly once

This closure hands its capture out when called, so the call uses it up — once is all it has:

fn make_ticket() -> impl FnOnce() -> String {
    let seat = String::from("7A");
    move || format!("seat {seat}")
}
let ticket = make_ticket();
println!("{}", ticket());

Example

fn make_greeter() -> impl Fn() {
    let word = String::from("hello");
    move || println!("{word}, learner!")
}
fn make_counter() -> impl FnMut() -> i32 {
    let mut count = 0;
    move || {
        count += 1;
        count
    }
}
fn make_message() -> impl FnOnce() -> String {
    let text = String::from("one time only");
    move || text
}
fn main() {
    let greet = make_greeter();
    greet();
    greet();
    let mut counter = make_counter();
    println!("count = {}", counter());
    println!("count = {}", counter());
    let once = make_message();
    println!("message = {}", once());
}

Key points

  • A closure that only borrowed a local variable could not be returned.
  • The local is dropped when the function ends.
  • The borrow would point at nothing.
  • One function returns one fixed hidden type.
  • Two different closures cannot take turns being the return value.
  • Different closures cannot share one Vec as impl Fn.
  • make_counter() hands out a fresh counter every call.
  • Each counter has its own private count inside.
  • The closure is a piece of behaviour, just as a returned number is a piece of data.
  • A returned closure hands behaviour back to the caller to run later.

In this example, make_greeter builds the String word inside the function. It then returns a move closure that has taken word with it. greet can be called twice. Both calls print hello, learner!. make_counter returns an FnMut closure that owns its own count. Stored in a mut variable, it prints count = 1, then count = 2. make_message returns an FnOnce closure that hands its text out. So it can run only once. It prints message = one time only. In all three, move is what lets the closure outlive the function that made it. The Function Pointers lesson shows the Box<dyn Fn> fix.

Note

Returning move closures is how factory functions are built in Rust.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

Why must a returned closure use move?