Skip to content

Commit 73ee34a

Browse files
committed
Add RFC 'closure_to_fn_coercion'
1 parent 3d81702 commit 73ee34a

File tree

2 files changed

+165
-2
lines changed

2 files changed

+165
-2
lines changed

text/0000-closure-to-fn-coercion.md

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
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

text/0401-coercions.md

+10-2
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,9 @@ Coercion is allowed between the following types:
154154

155155
* `&mut T` to `*mut T`
156156

157+
* `T` to `fn` if `T` is a closure that does not capture any local variables
158+
in its environment.
159+
157160
* `T` to `U` if `T` implements `CoerceUnsized<U>` (see below) and `T = Foo<...>`
158161
and `U = Foo<...>` (for any `Foo`, when we get HKT I expect this could be a
159162
constraint on the `CoerceUnsized` trait, rather than being checked here)
@@ -338,7 +341,7 @@ and where unsize_kind(`T`) is the kind of the unsize info
338341
in `T` - the vtable for a trait definition (e.g. `fmt::Display` or
339342
`Iterator`, not `Iterator<Item=u8>`) or a length (or `()` if `T: Sized`).
340343

341-
Note that lengths are not adjusted when casting raw slices -
344+
Note that lengths are not adjusted when casting raw slices -
342345
`T: *const [u16] as *const [u8]` creates a slice that only includes
343346
half of the original memory.
344347

@@ -441,4 +444,9 @@ Specifically for the DST custom coercions, the compiler could throw an error if
441444
it finds a user-supplied implementation of the `Unsize` trait, rather than
442445
silently ignoring them.
443446

444-
# Unresolved questions
447+
# Amendments
448+
449+
* Updated by [#1558](https://github.com/rust-lang/rfcs/pull/1558), which allows
450+
coercions from a non-capturing closure to a function pointer.
451+
452+
# Unresolved questions

0 commit comments

Comments
 (0)