TSK
TSK

Reputation: 751

Can I specify a closure which captures nothing?

While closures are powerful, they can lead to bugs if used carelessly. An example is a closure that modifies its captured variables when this is not wanted. Sometimes we only need an anonymous function that has no free variables. That is, it captures nothing. Can I specify a closure as capturing nothing and get this checked by the compiler? Something like none |...| {...} looks ideal. I know I can define a function in current scope but it's not as elegant as a variable containing an anonymous function.

Upvotes: 2

Views: 1307

Answers (3)

Chayim Friedman
Chayim Friedman

Reputation: 71605

Only non capturing closures can coerce to function pointers. You can use this fact to check that the closure is non-capturing by attempting to coerce it to a function pointer:

let closure = || {};
let _: fn() = closure;

You can wrap it in a macro to make this convenient:

macro_rules! enforce_non_capturing {
    // This macro does not allow specifying the return type (`|| -> Ret {}`),
    // but it can be adjusted to allow that.
    // It also does not allow patterns as parameter names, but allowing that
    // is harder.
    (
        | $( $param:ident $( : $param_ty:ty )? ),* $(,)? | $body:expr
    ) => {{
        let closure = | $( $param $( : $param_ty )?, )* | $body;
        // We want to generate `fn(_, _, ...) -> _` with underscores as the number of parameters.
        // We use a dummy repetition to achieve that.
        let _: fn( $( enforce_non_capturing!(@replace_with_underscore $param ), )* ) -> _ = closure;
        closure
    }};
    // `||` is recognized as one token instead of two, so we need another arm.
    ( || $body:expr ) => { enforce_non_capturing!(| | $body) };
    (@replace_with_underscore $param:ident) => { _ };
}

fn main() {
    // Compiles.
    let closure = enforce_non_capturing!(|| {});
    closure();
    let a = 0;
    // Doesn't compile.
    // let closure = enforce_non_capturing!(|| a);
    // closure();
}

Playground.

Upvotes: 4

Finomnis
Finomnis

Reputation: 22838

While this is most certainly not a 'best practice', you can store the closure in a function pointer. Function pointers can only hold non-capturing closures.

This works:

fn main() {
    let c: fn() = || {
        println!("Hello world!");
    };

    c();
}
Hello world!

While this doesn't:

fn main() {
    let mut a = 10;
    let c: fn() = || {
        a += 1;
    };
}
error[E0308]: mismatched types
 --> src/main.rs:3:19
  |
3 |       let c: fn() = || {
  |  ____________----___^
  | |            |
  | |            expected due to this
4 | |         a += 1;
5 | |     };
  | |_____^ expected fn pointer, found closure
  |
  = note: expected fn pointer `fn()`
                found closure `[closure@src/main.rs:3:19: 3:21]`
note: closures can only be coerced to `fn` types if they do not capture any variables
 --> src/main.rs:4:9
  |
4 |         a += 1;
  |         ^ `a` captured here

Upvotes: 1

prog-fh
prog-fh

Reputation: 16920

Maybe the constraint could not be expressed where the closure/function is provided, but where it is expected.

In this example, fnct2() cannot receive a closure.

fn fnct1(mut f: impl FnMut(i32) -> i32) {
    println!("{}", f(10));
}

fn fnct2(f: fn(i32) -> i32) {
    println!("{}", f(20));
}

fn main() {
    let mut x = 0;
    fnct1(|n| {
        x += 1;
        n + x
    });
    fnct2(|n| {
        // x += 2; // ERROR: expected fn pointer, found closure
        // n + x
        n + 2
    });
    println!("x={}", x);
}
/*
11
22
x=1
*/

Upvotes: 2

Related Questions