Difference of Blocks vs Closures in Rust

This post breaks down their differences, when to use each, and how they compare to Go’s func().

Author Avatar

wonjoon

  ·  3 min read

Expression Blocks vs Closures #

Key Differences #

FeatureExpression BlockClosure
What it isInline calculation blockAnonymous function
Execution timingImmediatelyWhen called
ParametersNot allowedAllowed
Captures outer variablesNoYes
ReusableOne-timeYes
Passable to other funcsNoYes
Return valueLast expressionLast expression

Expression Blocks: Inline One-Time Calculations #

let y = {
    let x = 10;
    x + 5
};
println!("{y}"); // 15
  • Block used just for computing a value
  • Executed immediately
  • Returns the last expression (no semicolon)
  • Does not take parameters or capture external variables
  • Great for one-off logic that doesn’t need to be reused

When to Use Expression Blocks

  • You need a value from a mini logic block
  • You don’t need parameters
  • You want instant evaluation
  • You’re not planning to reuse the logic

Example: Using if, match, or even loop as a value

let is_even = {
    let n = 4;
    if n % 2 == 0 { true } else { false }
};

let result = match 2 {
    1 => "one",
    2 => "two",
    _ => "many",
};

let loop_value = {
    let mut n = 0;
    loop {
        n += 1;
        if n == 3 { break n * 10; }
    }
};
// loop_value == 30

Note: while and for are statements, not expressions — they can’t be used like this.

Closures: Anonymous Functions for Reuse and Flexibility #

let add = |a: i32, b: i32| a + b;
let result = add(3, 4); // 7
  • Similar to anonymous functions in Go, JavaScript, etc.
  • Executed only when called
  • Can take parameters and capture external variables
  • Useful when passing logic into another function (e.g. map, filter)
  • Can be reused multiple times

When to Use Closures

  • You need to reuse logic
  • You want to pass logic as a parameter to another function
  • You need to delay execution
  • You need to capture external values

Use Case 1. Parameterized Logic

let square = |x| x * x;
println!("{}", square(5)); // 25

Use Case 2. Passing to Higher-Order Functions

let nums = vec![1, 2, 3];
let doubled: Vec<_> = nums.iter().map(|x| x * 2).collect();
// Result: [2, 4, 6]

Use Case 3. Threaded or Async Code

std::thread::spawn(|| {
    println!("Hello from thread");
});

Use Case 4. Capturing External Variables

let greeting = "Hi";
let say_hello = |name| format!("{greeting}, {name}");
println!("{}", say_hello("Alice")); // "Hi, Alice"

Comparison with Go #

Go only has function literals (func() { ... }) — no concept of expression blocks as values.

val := func() {
    fmt.Println("hello")
}
val() // Must be called

In Rust:

  • Use closures for what you’d use func() { ... } in Go
  • Use expression blocks for inline logic to assign values quickly

Summary: When to Use What? #

SituationUseWhy
Simple inline calculationExpression blockLess syntax, faster execution
Passing logic to another functionClosureClosures are first-class citizens
Accepting dynamic inputsClosureAccepting dynamic inputs
Passing logic to another functionClosureClosures take parameters
Referencing external valuesClosureCan capture and use outer scope
Like func() in GoClosureClosures = Go’s anonymous functions