Add RFC 'closure_to_fn_coercion'

This commit is contained in:
archshift 2016-03-25 15:45:51 -07:00
parent 3d8170295f
commit 73ee34aada
2 changed files with 165 additions and 2 deletions

View File

@ -0,0 +1,155 @@
- Feature Name: closure_to_fn_coercion
- Start Date: 2016-03-25
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)
# Summary
[summary]: #summary
A non-capturing (that is, does not `Clone` or `move` any local variables) should be
coercable to a function pointer (`fn`).
# Motivation
[motivation]: #motivation
Currently in rust, it is impossible to bind anything but a pre-defined function
as a function pointer. When dealing with closures, one must either rely upon
rust's type-inference capabilities, or use the `Fn` trait to abstract for any
closure with a certain type signature.
What is not possible, though, is to define a function while at the same time
binding it to a function pointer.
This is mainly used for convenience purposes, but in certain situations
the lack of ability to do so creates a significant amount of boilerplate code.
For example, when attempting to create an array of small, simple, but unique functions,
it would be necessary to pre-define each and every function beforehand:
```rust
fn inc_0(var: &mut u32) {}
fn inc_1(var: &mut u32) { *var += 1; }
fn inc_2(var: &mut u32) { *var += 2; }
fn inc_3(var: &mut u32) { *var += 3; }
const foo: [fn(&mut u32); 4] = [
inc_0,
inc_1,
inc_2,
inc_3,
];
```
This is a trivial example, and one that might not seem too consequential, but the
code doubles with every new item added to the array. With very many elements,
the duplication begins to seem unwarranted.
Another option, of course, is to use an array of `Fn` instead of `fn`:
```rust
const foo: [&'static Fn(&mut u32); 4] = [
&|var: &mut u32| {},
&|var: &mut u32| *var += 1,
&|var: &mut u32| *var += 2,
&|var: &mut u32| *var += 3,
];
```
And this seems to fix the problem. Unfortunately, however, looking closely one
can see that because we use the `Fn` trait, an extra layer of indirection
is added when attempting to run `foo[n](&mut bar)`.
Rust must use dynamic dispatch because a closure is secretly a struct that
contains references to captured variables, and the code within that closure
must be able to access those references stored in the struct.
In the above example, though, no variables are captured by the closures,
so in theory nothing would stop the compiler from treating them as anonymous
functions. By doing so, unnecessary indirection would be avoided. In situations
where this function pointer array is particularly hot code, the optimization
would be appreciated.
# Detailed design
[design]: #detailed-design
In C++, non-capturing lambdas (the C++ equivalent of closures) "decay" into function pointers
when they do not need to capture any variables. This is used, for example, to pass a lambda
into a C function:
```cpp
void foo(void (*foobar)(void)) {
// impl
}
void bar() {
foo([]() { /* do something */ });
}
```
With this proposal, rust users would be able to do the same:
```rust
fn foo(foobar: fn()) {
// impl
}
fn bar() {
foo(|| { /* do something */ });
}
```
Using the examples within ["Motivation"](#motivation), the code array would
be simplified to no performance detriment:
```rust
const foo: [fn(&mut u32); 4] = [
|var: &mut u32| {},
|var: &mut u32| *var += 1,
|var: &mut u32| *var += 2,
|var: &mut u32| *var += 3,
];
```
# Drawbacks
[drawbacks]: #drawbacks
To a rust user, there is no drawback to this new coercion from closures to `fn` types.
The only drawback is that it would add some amount of complexity to the type system.
# Alternatives
[alternatives]: #alternatives
## Anonymous function syntax
With this alternative, rust users would be able to directly bind a function
to a variable, without needing to give the function a name.
```rust
let foo = fn() { /* do something */ };
foo();
```
```rust
const foo: [fn(&mut u32); 4] = [
fn(var: &mut u32) {},
fn(var: &mut u32) { *var += 1 },
fn(var: &mut u32) { *var += 2 },
fn(var: &mut u32) { *var += 3 },
];
```
This isn't ideal, however, because it would require giving new semantics
to `fn` syntax.
## Aggressive optimization
This is possibly unrealistic, but an alternative would be to continue encouraging
the use of closures with the `Fn` trait, but conduct heavy optimization to determine
when the used closure is "trivial" and does not need indirection.
Of course, this would probably significantly complicate the optimization process, and
would have the detriment of not being easily verifiable by the programmer without
checking the disassembly of their program.
# Unresolved questions
[unresolved]: #unresolved-questions
None

View File

@ -154,6 +154,9 @@ Coercion is allowed between the following types:
* `&mut T` to `*mut T`
* `T` to `fn` if `T` is a closure that does not capture any local variables
in its environment.
* `T` to `U` if `T` implements `CoerceUnsized<U>` (see below) and `T = Foo<...>`
and `U = Foo<...>` (for any `Foo`, when we get HKT I expect this could be a
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
in `T` - the vtable for a trait definition (e.g. `fmt::Display` or
`Iterator`, not `Iterator<Item=u8>`) or a length (or `()` if `T: Sized`).
Note that lengths are not adjusted when casting raw slices -
Note that lengths are not adjusted when casting raw slices -
`T: *const [u16] as *const [u8]` creates a slice that only includes
half of the original memory.
@ -441,4 +444,9 @@ Specifically for the DST custom coercions, the compiler could throw an error if
it finds a user-supplied implementation of the `Unsize` trait, rather than
silently ignoring them.
# Unresolved questions
# Amendments
* Updated by [#1558](https://github.com/rust-lang/rfcs/pull/1558), which allows
coercions from a non-capturing closure to a function pointer.
# Unresolved questions