|
| 1 | +- Feature Name: closure_to_fn_coercion |
| 2 | +- Start Date: 2016-03-25 |
| 3 | +- RFC PR: (leave this empty) |
| 4 | +- Rust Issue: (leave this empty) |
| 5 | + |
| 6 | +# Summary |
| 7 | +[summary]: #summary |
| 8 | + |
| 9 | +A non-capturing (that is, does not `Clone` or `move` any local variables) should be |
| 10 | +coercable to a function pointer (`fn`). |
| 11 | + |
| 12 | +# Motivation |
| 13 | +[motivation]: #motivation |
| 14 | + |
| 15 | +Currently in rust, it is impossible to bind anything but a pre-defined function |
| 16 | +as a function pointer. When dealing with closures, one must either rely upon |
| 17 | +rust's type-inference capabilities, or use the `Fn` trait to abstract for any |
| 18 | +closure with a certain type signature. |
| 19 | + |
| 20 | +What is not possible, though, is to define a function while at the same time |
| 21 | +binding it to a function pointer. |
| 22 | + |
| 23 | +This is mainly used for convenience purposes, but in certain situations |
| 24 | +the lack of ability to do so creates a significant amount of boilerplate code. |
| 25 | +For example, when attempting to create an array of small, simple, but unique functions, |
| 26 | +it would be necessary to pre-define each and every function beforehand: |
| 27 | + |
| 28 | +```rust |
| 29 | +fn inc_0(var: &mut u32) {} |
| 30 | +fn inc_1(var: &mut u32) { *var += 1; } |
| 31 | +fn inc_2(var: &mut u32) { *var += 2; } |
| 32 | +fn inc_3(var: &mut u32) { *var += 3; } |
| 33 | + |
| 34 | +const foo: [fn(&mut u32); 4] = [ |
| 35 | + inc_0, |
| 36 | + inc_1, |
| 37 | + inc_2, |
| 38 | + inc_3, |
| 39 | +]; |
| 40 | +``` |
| 41 | + |
| 42 | +This is a trivial example, and one that might not seem too consequential, but the |
| 43 | +code doubles with every new item added to the array. With very many elements, |
| 44 | +the duplication begins to seem unwarranted. |
| 45 | + |
| 46 | +Another option, of course, is to use an array of `Fn` instead of `fn`: |
| 47 | + |
| 48 | +```rust |
| 49 | +const foo: [&'static Fn(&mut u32); 4] = [ |
| 50 | + &|var: &mut u32| {}, |
| 51 | + &|var: &mut u32| *var += 1, |
| 52 | + &|var: &mut u32| *var += 2, |
| 53 | + &|var: &mut u32| *var += 3, |
| 54 | +]; |
| 55 | +``` |
| 56 | + |
| 57 | +And this seems to fix the problem. Unfortunately, however, looking closely one |
| 58 | +can see that because we use the `Fn` trait, an extra layer of indirection |
| 59 | +is added when attempting to run `foo[n](&mut bar)`. |
| 60 | + |
| 61 | +Rust must use dynamic dispatch because a closure is secretly a struct that |
| 62 | +contains references to captured variables, and the code within that closure |
| 63 | +must be able to access those references stored in the struct. |
| 64 | + |
| 65 | +In the above example, though, no variables are captured by the closures, |
| 66 | +so in theory nothing would stop the compiler from treating them as anonymous |
| 67 | +functions. By doing so, unnecessary indirection would be avoided. In situations |
| 68 | +where this function pointer array is particularly hot code, the optimization |
| 69 | +would be appreciated. |
| 70 | + |
| 71 | +# Detailed design |
| 72 | +[design]: #detailed-design |
| 73 | + |
| 74 | +In C++, non-capturing lambdas (the C++ equivalent of closures) "decay" into function pointers |
| 75 | +when they do not need to capture any variables. This is used, for example, to pass a lambda |
| 76 | +into a C function: |
| 77 | + |
| 78 | +```cpp |
| 79 | +void foo(void (*foobar)(void)) { |
| 80 | + // impl |
| 81 | +} |
| 82 | +void bar() { |
| 83 | + foo([]() { /* do something */ }); |
| 84 | +} |
| 85 | +``` |
| 86 | +
|
| 87 | +With this proposal, rust users would be able to do the same: |
| 88 | +
|
| 89 | +```rust |
| 90 | +fn foo(foobar: fn()) { |
| 91 | + // impl |
| 92 | +} |
| 93 | +fn bar() { |
| 94 | + foo(|| { /* do something */ }); |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +Using the examples within ["Motivation"](#motivation), the code array would |
| 99 | +be simplified to no performance detriment: |
| 100 | + |
| 101 | +```rust |
| 102 | +const foo: [fn(&mut u32); 4] = [ |
| 103 | + |var: &mut u32| {}, |
| 104 | + |var: &mut u32| *var += 1, |
| 105 | + |var: &mut u32| *var += 2, |
| 106 | + |var: &mut u32| *var += 3, |
| 107 | +]; |
| 108 | +``` |
| 109 | + |
| 110 | +# Drawbacks |
| 111 | +[drawbacks]: #drawbacks |
| 112 | + |
| 113 | +To a rust user, there is no drawback to this new coercion from closures to `fn` types. |
| 114 | + |
| 115 | +The only drawback is that it would add some amount of complexity to the type system. |
| 116 | + |
| 117 | +# Alternatives |
| 118 | +[alternatives]: #alternatives |
| 119 | + |
| 120 | +## Anonymous function syntax |
| 121 | + |
| 122 | +With this alternative, rust users would be able to directly bind a function |
| 123 | +to a variable, without needing to give the function a name. |
| 124 | + |
| 125 | +```rust |
| 126 | +let foo = fn() { /* do something */ }; |
| 127 | +foo(); |
| 128 | +``` |
| 129 | + |
| 130 | +```rust |
| 131 | +const foo: [fn(&mut u32); 4] = [ |
| 132 | + fn(var: &mut u32) {}, |
| 133 | + fn(var: &mut u32) { *var += 1 }, |
| 134 | + fn(var: &mut u32) { *var += 2 }, |
| 135 | + fn(var: &mut u32) { *var += 3 }, |
| 136 | +]; |
| 137 | +``` |
| 138 | + |
| 139 | +This isn't ideal, however, because it would require giving new semantics |
| 140 | +to `fn` syntax. |
| 141 | + |
| 142 | +## Aggressive optimization |
| 143 | + |
| 144 | +This is possibly unrealistic, but an alternative would be to continue encouraging |
| 145 | +the use of closures with the `Fn` trait, but conduct heavy optimization to determine |
| 146 | +when the used closure is "trivial" and does not need indirection. |
| 147 | + |
| 148 | +Of course, this would probably significantly complicate the optimization process, and |
| 149 | +would have the detriment of not being easily verifiable by the programmer without |
| 150 | +checking the disassembly of their program. |
| 151 | + |
| 152 | +# Unresolved questions |
| 153 | +[unresolved]: #unresolved-questions |
| 154 | + |
| 155 | +None |
0 commit comments