rokdocs
v0.1

4.8. Trailing closures

When the last argument of a function or method call is a closure, the closure may be moved outside the parentheses. This form is called a trailing closure.

4.8.1. Form

The grammar of trailing closures is:

trailing_call   ::= primary "(" arg_list? ")" trailing_closure
                  | primary trailing_closure                    // 0-arg call
trailing_closure ::= ("|" closure_params? "|" ("->" type)?)? block
function(args) |params| { body }
function(args) { body }            // zero-parameter shorthand

The closure directly follows the call parentheses, and the body is a block from block expressions.

Zero-Parameter shorthand: { body }

A closure without parameters may be shortened from || { body } to { body }.

let h = thread::spawn { heavy_work() };
// == thread::spawn(|| { heavy_work() })

with_lock(mutex) { critical_section() };

This shorthand has meaning only for a { ... } directly after a function call. A { ... } anywhere else is an ordinary block expression from block expressions.

One or more parameters

With one or more parameters, write:

let doubled = xs.map |x| { x * 2 };
let pos = xs.filter |x| { x > 0 };
let opened = with_file(path) |f| {
    let data = f.read();
    process(data)
};
pairs.for_each |k, v| { print(k, v) };

The pipes (|...|) go outside the parentheses, directly before the body. The body is always a block: always |x| { body }, never a one-line |x| body.

Trailing closures use block bodies by default for readability and consistency.

Rules for the parenthesis-free form

Attaching a closure without call parentheses, as in xs.map |x| { .. }, is a trailing closure only under these conditions:

  • It is immediately preceded by a path (thread::spawn, f) or a member name (.map).
  • It is followed by |params|, an optional -> R, and {. Without this shape, | is bitwise OR.
  • || is always logical OR. A closure without parameters uses the { body } shorthand.
  • Inside the head (condition and scrutinee) of if / while / match, parenthesis-free trailing closures and the { body } shorthand are disabled; a { there opens the body block. If a closure is needed in the head, use parentheses (if xs.any(|x| { x > 0 }) { .. }).
let ys = xs.map |x| -> i64 { x as i64 * 2 };   // explicit return type
let z = a | b;                                  // bitwise OR - not followed by the `|params| {` shape
if ok || retry { .. }                           // logical OR

4.8.2. Free choice with explicit parentheses

Trailing closures are permitted, not required.

xs.map(|x| x * 2);                // explicit parentheses - OK
xs.map |x| { x * 2 };              // trailing - OK

Teams and users may choose freely according to their style.

4.8.3. When the closure is not the last argument

A trailing closure can fill only the last argument position. If the closure is a middle argument, explicit parentheses must be used.

fn process(input: str, transform: fn(str) -> str, output: str) -> str { ... }

process("in", |s| { s.to_upper() }, "out");           // explicit parentheses (middle argument)
process("in", "out") |s| { s.to_upper() };            // ERROR - trailing only in the last position

4.8.4. Calls with zero arguments

When the call has no other arguments, the empty parentheses may also be omitted.

thread::spawn { heavy() };        // same as thread::spawn() { heavy() }
                                   // == thread::spawn(|| { heavy() })

When the only parameter is a closure, as with thread::spawn, this is the most natural call form.

4.8.5. Nesting

Trailing closures may be nested freely.

par::scope |s| {
    s.spawn { f() };
    s.spawn { g() };
};

The same shorthand rule ({ body } ↔ || { body }) applies to inner closures.

4.8.6. Resolving parser ambiguity

Trailing closures introduce an ambiguity where some code could be read as either a block expression or a trailing closure. The following rules resolve it.

PositionMeaning
{ ... } after a control keyword (if, else, for, while, loop, match)control body
{ ... } directly after a function calltrailing closure (0-arg)
|params| { ... } directly after a function calltrailing closure (1+ arg)
{ ... } in an ordinary expression position such as x = { ... };block expression (evaluated immediately) - a 0-arg closure needs explicit || { ... }
|...| { ... } inside an expressionordinary closure expression

In short: a { ... } or |...| { ... } directly after a function call ((args)), with or without whitespace, is a trailing closure.

4.8.7. A lone {}: ordinary block vs 0-Arg closure

let x = { 42 };                   // block expression (value 42)
let f = { 42 };                   // ??

These two lines have the same form; which meaning takes precedence?

The rule: a { body } appearing alone in expression position is a block expression.

If a 0-arg closure is intended, write || { body } explicitly.

let f = || { 42 };                // explicit 0-arg closure
let r = f();                       // call

That is, only a { body } directly after a function call is recognized as a trailing closure.

4.8.8. Closures in statement-like positions such as par for

For parallel loops, transactions, context managers and the like, trailing closures produce DSL-friendly forms that read like keywords.

par::for_each(xs) |x| { ... }     // looks like par for
with_lock(mutex) { ... }           // looks like a with-statement
retry(3) { ... }                   // looks like a retry keyword

This is consistent with the principle of minimizing reserved keywords in keywords. Combining library functions with trailing closures yields patterns that read like control flow.

4.8.9. Combining with named arguments

fn run(name: str = "task", body: fn() -> ()) { ... }

run("compute") { ... };           // positional + trailing OK
run(name: "compute") { ... };     // named + trailing OK

Positional arguments, named arguments and trailing closures may be combined freely.

4.8.10. Summary

Trailing closures allow DSL-like expressions at the specification level without additional keywords. The key point is that users can build patterns that look like language keywords directly as libraries.

Edit this page on GitHub