Practical examples of examples of using closures in Rust

If you write Rust for more than a weekend, you’ll run into closures. They sit at the center of iterator adapters, async code, and a lot of modern Rust APIs. In this guide, we’ll walk through practical, real-world examples of examples of using closures in Rust so the syntax stops feeling mysterious and starts feeling like a tool you reach for on purpose. Instead of abstract theory, we’ll look at how closures behave with ownership, lifetimes, and traits like `Fn`, `FnMut`, and `FnOnce` in realistic scenarios: data processing, configuration, callbacks, async code, and more. Along the way, we’ll highlight patterns that working Rust developers actually use in 2024–2025, and why the best examples tend to lean on closures for flexibility without sacrificing performance. If you’ve ever wondered when to choose a closure over a normal function, or how to avoid common borrow-checker traps, these examples of closures in Rust will give you a clear mental model.
Written by
Jamie
Published

Quick, concrete examples of using closures in Rust

Let’s start with a few quick-hit examples of examples of using closures in Rust, then unpack why they work and where they shine.

fn main() {
    // Inline filtering with a closure
    let nums = vec![1, 2, 3, 4, 5, 6];
    let evens: Vec<_> = nums
        .into_iter()
        .filter(|n| n % 2 == 0)
        .collect();

    // Capturing an external variable
    let threshold = 10;
    let bigger_than_threshold = |x: i32| x > threshold;

    println!("evens: {:?}", evens);
    println!("15 > threshold? {}", bigger_than_threshold(15));
}

These tiny snippets already hint at the real examples you’ll see across the Rust ecosystem: closures passed into iterator methods, closures capturing configuration, and closures used as callbacks.


Iterator-heavy examples of using closures in Rust

The most common example of closures in Rust is with iterators. If you’ve ever chained map, filter, or fold, you’ve already leaned on closures.

Mapping and transforming data

Rust’s iterator adapters are designed around closures. Here’s a realistic snippet that might appear in data processing or ETL-style code:

#[derive(Debug)]
struct User {
    id: u64,
    email: String,
    active: bool,
}

fn active_user_emails(users: &[User]) -> Vec<String> {
    users
        .iter()
        .filter(|u| u.active)
        .map(|u| u.email.to_lowercase())
        .collect()
}

In this example of closure usage, the filter and map calls each receive a closure. The closures capture nothing by value; they simply borrow u from the iterator. These are some of the best examples for beginners because they highlight how closures can stay lightweight and inline without cluttering the codebase with tiny named functions.

Aggregation with fold

Another classic example of examples of using closures in Rust is fold, which lets you accumulate a value as you iterate:

fn sum_of_squares(nums: &[i32]) -> i32 {
    nums.iter().fold(0, |acc, n| acc + n * n)
}

The closure |acc, n| acc + n * n is passed directly to fold. It’s a pure function with no side effects, and because it’s a closure, it can capture environment if needed. In practice, this pattern appears in numeric code, log aggregation, and any place you’d otherwise write a loop with a running total.


Capturing environment: configuration and partial application

Real examples of closures in Rust almost always involve capturing some local configuration or state.

Closure as a configurable predicate

Imagine you’re writing a small filtering utility where the threshold is determined at runtime:

fn filter_above(nums: &[i32], threshold: i32) -> Vec<i32> {
    let is_above = |x: &i32| *x > threshold; // captures `threshold` by borrow

    nums.iter().copied().filter(is_above).collect()
}

Here the closure is_above closes over threshold. No global static, no extra struct; just a tiny, localized behavior customized by the surrounding scope. This pattern shows up in search filters, validation logic, and feature-flag checks.

Simulating partial application

Rust doesn’t have built-in currying, but closures give you a nice approximation:

fn make_multiplier(factor: i32) -> impl Fn(i32) -> i32 {
    move |x| x * factor
}

fn main() {
    let times_two = make_multiplier(2);
    let times_ten = make_multiplier(10);

    println!("2 * 7 = {}", times_two(7));
    println!("10 * 3 = {}", times_ten(3));
}

This is one of the best examples of using closures in Rust to build small domain-specific languages (DSL-style APIs). Libraries for configuration, pricing rules, or routing often expose functions that return closures like this.


Fn, FnMut, FnOnce: real examples from everyday Rust

You can’t talk about examples of examples of using closures in Rust without touching Fn, FnMut, and FnOnce. The names look academic, but the patterns are very down-to-earth.

Fn: read-only access

Closures that only read from captured variables implement Fn and can be called multiple times without mutation:

fn apply_twice<F>(f: F, x: i32) -> i32
where
    F: Fn(i32) -> i32,
{
    f(f(x))
}

fn main() {
    let add_one = |n| n + 1;
    println!("{}", apply_twice(add_one, 5)); // 7
}

This pattern shows up in retry logic, middleware stacks, and any API that needs a pure transformation.

FnMut: closures that mutate captured state

A more interesting example of using closures in Rust is when the closure needs to update something it captures:

fn call_with_logging<F>(mut f: F)
where
    F: FnMut(i32) -> i32,
{
    let inputs = [1, 2, 3];
    for input in inputs {
        let output = f(input);
        println!("f({}) = {}", input, output);
    }
}

fn main() {
    let mut call_count = 0;
    let mut tracked = |x: i32| {
        call_count += 1; // mutation of captured variable
        x * 2
    };

    call_with_logging(&mut tracked);
    println!("call_count = {}", call_count);
}

This is a real example you might see in stateful iterators, metrics collection, or caching layers.

FnOnce: consuming captured values

Sometimes the closure consumes what it captures and can only be called once. That’s where FnOnce comes in.

fn consume_and_print<F>(f: F)
where
    F: FnOnce(),
{
    f();
}

fn main() {
    let s = String::from("hello");

    let consume_s = move || {
        // `s` is moved into the closure
        println!("{}", s);
    };

    consume_and_print(consume_s);
    // can't use `consume_s` or `s` here
}

These examples include patterns you’ll see in thread::spawn, async executors, and one-shot callbacks where ownership must move into the closure.


Examples of using closures in Rust for callbacks and handlers

Closures make for ergonomic callbacks, especially in async and event-driven code.

Thread spawning and ownership

One widely taught example of using closures in Rust is std::thread::spawn, which takes a FnOnce closure by value:

use std::thread;

fn main() {
    let data = vec![1, 2, 3, 4];

    let handle = thread::spawn(move || {
        let sum: i32 = data.iter().sum();
        println!("sum in thread: {}", sum);
    });

    handle.join().unwrap();
}

The move keyword forces the closure to take ownership of data, matching the thread’s 'static lifetime requirement. This is not just a toy; it’s exactly how production Rust services pass work to background threads.

Simple async-style callback pattern

Even without a full async runtime, you can sketch a callback-based API with closures:

fn simulate_network_request<F>(url: &str, on_complete: F)
where
    F: FnOnce(Result<String, String>),
{
    // pretend we did some I/O
    let response = format!("response from {}", url);
    on_complete(Ok(response));
}

fn main() {
    let url = "https://example.com";

    simulate_network_request(url, |result| match result {
        Ok(body) => println!("Success: {}", body),
        Err(err) => eprintln!("Error: {}", err),
    });
}

Modern async Rust often wraps this pattern in Futures, but the underlying idea—passing behavior as a closure—remains the same.

For deeper background on closures and ownership, the official Rust Book’s section on closures is still a gold standard reference: https://doc.rust-lang.org/book/ch13-01-closures.html.


Iterator pipelines: best examples of expressive Rust with closures

Some of the best examples of examples of using closures in Rust come from iterator pipelines that read almost like SQL or dataflow descriptions.

Combining filter, map, and group-like behavior

Consider log lines where you want to:

  • Keep only ERROR entries
  • Extract a code from each line
  • Count frequency of each code
use std::collections::HashMap;

fn error_code_counts(lines: &[String]) -> HashMap<String, usize> {
    let mut counts = HashMap::new();

    lines
        .iter()
        .filter(|line| line.contains("ERROR"))
        .filter_map(|line| {
            line.split_whitespace()
                .find(|part| part.starts_with("code="))
                .map(|code_part| code_part.trim_start_matches("code=").to_string())
        })
        .for_each(|code| {

            *counts.entry(code).or_insert(0) += 1;
        });

    counts
}

Every step here is an example of using closures in Rust to express intent right where it’s needed—no extra helper functions, no boilerplate.


Higher-order functions: closures as parameters and return values

Rust leans heavily on higher-order functions, especially in libraries that want to stay generic and flexible.

Passing closures into generic helpers

Here’s a pattern you’ll see in real-world code that needs pluggable behavior:

fn with_retry<F, T, E>(mut op: F, retries: u32) -> Result<T, E>
where
    F: FnMut() -> Result<T, E>,
{
    let mut attempts = 0;
    loop {
        attempts += 1;
        match op() {
            Ok(v) => return Ok(v),
            Err(e) if attempts > retries => return Err(e),
            Err(_) => continue,
        }
    }
}

fn main() {
    let mut counter = 0;

    let result = with_retry(
        || {
            counter += 1;
            if counter < 3 {
                Err("not yet")
            } else {
                Ok("success")
            }
        },
        5,
    );

    println!("result: {:?}", result);
}

This is a strong example of examples of using closures in Rust for infrastructure code: retries, backoff policies, and transactional operations.

Returning closures for flexible configuration

A library might expose a function that returns a preconfigured closure:

fn make_rate_limiter(max_per_minute: u32) -> impl FnMut() -> bool {
    use std::time::{Duration, Instant};

    let mut count = 0u32;
    let mut window_start = Instant::now();

    move || {
        let now = Instant::now();
        if now.duration_since(window_start) > Duration::from_secs(60) {
            window_start = now;
            count = 0;
        }
        if count < max_per_minute {
            count += 1;
            true
        } else {
            false
        }
    }
}

fn main() {
    let mut limiter = make_rate_limiter(100);
    assert!(limiter()); // allowed
}

This pattern shows up in API clients, job schedulers, and any system that wants to bundle policy with behavior.


2024–2025 context: how closures fit into modern Rust

In current Rust (1.80+ as of late 2024), closures continue to be the backbone of iterator-heavy and async-heavy code. Iterator adapters in std and crates like itertools lean on closures for expressive data transformation. Async ecosystems like tokio and async-std use closures in builder patterns, configuration hooks, and task spawning.

The Rust community’s best practices around closures are stable and well documented in resources like:

While these aren’t health or medical sites, they play a similar role for Rust that organizations like the National Institutes of Health (NIH) or Harvard do for scientific and educational topics: long-lived, vetted, and widely trusted.


FAQ: short answers with concrete examples

What are some simple examples of using closures in Rust?

Simple examples include filtering a vector with filter(|x| x % 2 == 0), mapping values with map(|x| x * 2), and sorting with a custom comparator like sort_by(|a, b| a.len().cmp(&b.len())). These show closures passed directly into iterator and slice methods.

Can you give an example of a closure capturing a variable?

Yes. A classic example of a capturing closure is:

let threshold = 10;
let is_large = |x: i32| x > threshold;

Here is_large captures threshold by reference and can use it each time the closure is called.

Why use a closure instead of a normal function in Rust?

Use a closure when you want behavior that depends on local variables without threading them through every call. In the best examples, closures keep related logic close together—like a small predicate or transformation defined right next to the loop or iterator chain that uses it.

Are closures in Rust slow compared to functions?

In most real examples, no. Closures are usually monomorphized and inlined just like generic functions. When you pass them as generic parameters (F: Fn(...)), the compiler can optimize them aggressively. Dynamic trait objects like Box<dyn Fn()> do add indirection, but that’s a choice you make explicitly.

Do closures work with async Rust?

Yes. You’ll see closures used with async executors (for example, tokio::spawn(async move { ... })), in builder methods that configure timeouts or middleware, and in callback-style APIs that wrap async operations. The same ownership rules apply; move closures are common when you need to transfer data into an async task.


If you keep these real examples of examples of using closures in Rust in mind—iterators, callbacks, configuration, and higher-order helpers—you’ll start to see closures not as syntactic sugar, but as a very direct way to express behavior right where it matters.

Explore More Rust Code Snippets

Discover more examples and insights in this category.

View All Rust Code Snippets