Reputation: 751
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
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();
}
Upvotes: 4
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
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