From 73ee34aada32725bda1401dbd1f251e72f5d00d6 Mon Sep 17 00:00:00 2001 From: archshift Date: Fri, 25 Mar 2016 15:45:51 -0700 Subject: [PATCH 1/4] Add RFC 'closure_to_fn_coercion' --- text/0000-closure-to-fn-coercion.md | 155 ++++++++++++++++++++++++++++ text/0401-coercions.md | 12 ++- 2 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 text/0000-closure-to-fn-coercion.md diff --git a/text/0000-closure-to-fn-coercion.md b/text/0000-closure-to-fn-coercion.md new file mode 100644 index 00000000000..8f9f95eb003 --- /dev/null +++ b/text/0000-closure-to-fn-coercion.md @@ -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 diff --git a/text/0401-coercions.md b/text/0401-coercions.md index 554ac61e11c..816879f4f89 100644 --- a/text/0401-coercions.md +++ b/text/0401-coercions.md @@ -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` (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`) 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 \ No newline at end of file From 1a22f6073b557a213bdd7a8067cf0a47f775d7e0 Mon Sep 17 00:00:00 2001 From: archshift Date: Mon, 7 Nov 2016 11:46:43 -0800 Subject: [PATCH 2/4] Clarify re-coercion, pidgeonhole drawback; fix typos --- text/0000-closure-to-fn-coercion.md | 65 +++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/text/0000-closure-to-fn-coercion.md b/text/0000-closure-to-fn-coercion.md index 8f9f95eb003..b2c18a55210 100644 --- a/text/0000-closure-to-fn-coercion.md +++ b/text/0000-closure-to-fn-coercion.md @@ -6,19 +6,19 @@ # Summary [summary]: #summary -A non-capturing (that is, does not `Clone` or `move` any local variables) should be -coercable to a function pointer (`fn`). +A non-capturing (that is, does not `Clone` or `move` any local variables) closure +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 +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 +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. +It is not possible 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. @@ -107,19 +107,66 @@ const foo: [fn(&mut u32); 4] = [ ]; ``` +Note that once explicitly assigned to an `Fn` trait, the closure can no longer be +coerced into `fn`, even if it has no captures. Just as we cannot do: + +```rust +let a: u32 = 0; // Coercion +let b: i32 = a; // Can't re-coerce +let x: *const u32 = &a; // Coercion +let y: &u32 = x; // Can't re-coerce +``` + +We can't similarly re-coerce a `Fn` trait. +```rust +let a: &Fn(u32) -> u32 = |foo: u32| { foo + 1 }; +let b: fn(u32) -> u32 = *a; // Can't re-coerce +``` + # Drawbacks [drawbacks]: #drawbacks -To a rust user, there is no drawback to this new coercion from closures to `fn` types. +This proposal could potentially allow Rust users to accidentally constrain their APIs. +In the case of a crate, a user accidentally returning `fn` instead of `Fn` may find +that their code compiles at first, but breaks when the user later needs to capture variables: + +```rust +// The specific syntax is more convenient to use +fn func_specific(&self) -> (fn() -> u32) { + || return 0 +} + +fn func_general<'a>(&'a self) -> impl Fn() -> u32 { + move || return self.field +} +``` + +In the above example, the API author could start off with the specific version of the function, +and by circumstance later need to capture a variable. The required change from `fn` to `Fn` could +be a breaking change. + +We do expect crate authors to measure their API's flexibility in other areas, however, as when +determining whether to take `&self` or `&mut self`. Taking a similar situation to the above: + +```rust +fn func_specific<'a>(&'a self) -> impl Fn() -> u32 { + move || return self.field +} + +fn func_general<'a>(&'a mut self) -> impl FnMut() -> u32 { + move || { self.field += 1; return self.field; } +} +``` -The only drawback is that it would add some amount of complexity to the type system. +This drawback is probably outweighed by convenience, simplicity, and the potential for optimization +that comes with the proposed changes, however. # Alternatives [alternatives]: #alternatives ## Anonymous function syntax -With this alternative, rust users would be able to directly bind a function +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 From d51becd78f00e89e7fef6c21cd5a61075e2026ca Mon Sep 17 00:00:00 2001 From: archshift Date: Sat, 7 Jan 2017 17:48:55 -0800 Subject: [PATCH 3/4] Include more "fn literal" alternative details, clear up language --- text/0000-closure-to-fn-coercion.md | 82 ++++++++++++++++++----------- 1 file changed, 51 insertions(+), 31 deletions(-) diff --git a/text/0000-closure-to-fn-coercion.md b/text/0000-closure-to-fn-coercion.md index b2c18a55210..bec4c0accdc 100644 --- a/text/0000-closure-to-fn-coercion.md +++ b/text/0000-closure-to-fn-coercion.md @@ -20,8 +20,8 @@ closure with a certain type signature. It is not possible 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. +This is, admittedly, a convenience-motivated feature, but in certain situations +the inability to bind code this way creates a significant amount of boilerplate. 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: @@ -40,10 +40,10 @@ const foo: [fn(&mut u32); 4] = [ ``` 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, +code doubles with every new item added to the array. With a large amount of elements, the duplication begins to seem unwarranted. -Another option, of course, is to use an array of `Fn` instead of `fn`: +A solution, of course, is to use an array of `Fn` instead of `fn`: ```rust const foo: [&'static Fn(&mut u32); 4] = [ @@ -54,19 +54,28 @@ const foo: [&'static Fn(&mut u32); 4] = [ ]; ``` -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)`. +And this seems to fix the problem. Unfortunately, however, because we use +a reference to 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. +Rust must use dynamic dispatch in this situation; a closure with captures is nothing +but a struct containing references to captured variables. The code associated with a +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. +In situations where this function pointer array is particularly hot code, +any optimizations would be appreciated. More generally, it is always preferable +to avoid unnecessary indirection. And, of course, it is impossible to use this syntax +when dealing with FFI. + +Aside from code-size nits, anonymous functions are legitimately useful for programmers. +In the case of callback-heavy code, for example, it can be impractical to define functions +out-of-line, with the requirement of producing confusing (and unnecessary) names for each. +In the very first example given, `inc_X` names were used for the out-of-line functions, but +more complicated behavior might not be so easily representable. + +Finally, this sort of automatic coercion is simply intuitive to the programmer. +In the `&Fn` example, no variables are captured by the closures, so the theory is +that nothing stops the compiler from treating them as anonymous functions. # Detailed design [design]: #detailed-design @@ -107,17 +116,15 @@ const foo: [fn(&mut u32); 4] = [ ]; ``` -Note that once explicitly assigned to an `Fn` trait, the closure can no longer be -coerced into `fn`, even if it has no captures. Just as we cannot do: +Because there does not exist any item in the language that directly produces +a `fn` type, even `fn` items must go through the process of reification. To +perform the coercion, then, rustc must additionally allow the reification of +unsized closures to `fn` types. The implementation of this is simplified by the +fact that closures' capture information is recorded on the type-level. -```rust -let a: u32 = 0; // Coercion -let b: i32 = a; // Can't re-coerce -let x: *const u32 = &a; // Coercion -let y: &u32 = x; // Can't re-coerce -``` +*Note:* once explicitly assigned to an `Fn` trait, the closure can no longer be +coerced into `fn`, even if it has no captures. -We can't similarly re-coerce a `Fn` trait. ```rust let a: &Fn(u32) -> u32 = |foo: u32| { foo + 1 }; let b: fn(u32) -> u32 = *a; // Can't re-coerce @@ -127,7 +134,7 @@ let b: fn(u32) -> u32 = *a; // Can't re-coerce [drawbacks]: #drawbacks This proposal could potentially allow Rust users to accidentally constrain their APIs. -In the case of a crate, a user accidentally returning `fn` instead of `Fn` may find +In the case of a crate, a user returning `fn` instead of `Fn` may find that their code compiles at first, but breaks when the user later needs to capture variables: ```rust @@ -158,13 +165,13 @@ fn func_general<'a>(&'a mut self) -> impl FnMut() -> u32 { } ``` -This drawback is probably outweighed by convenience, simplicity, and the potential for optimization -that comes with the proposed changes, however. +This aspect is probably outweighed by convenience, simplicity, and the potential for optimization +that comes with the proposed changes. # Alternatives [alternatives]: #alternatives -## Anonymous function syntax +## Function literal 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. @@ -184,12 +191,24 @@ const foo: [fn(&mut u32); 4] = [ ``` This isn't ideal, however, because it would require giving new semantics -to `fn` syntax. +to `fn` syntax. Additionally, such syntax would either require explicit return types, +or additional reasoning about the literal's return type. + +```rust +fn(x: bool) { !x } +``` + +The above function literal, at first glance, appears to return `()`. This could be +potentially misleading, especially in situations where the literal is bound to a +variable with `let`. + +As with all new syntax, this alternative would carry with it a discovery barrier. +Closure coercion may be preferred due to its intuitiveness. ## 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 +the use of closures with the `Fn` trait, but use static analysis to determine when the used closure is "trivial" and does not need indirection. Of course, this would probably significantly complicate the optimization process, and @@ -199,4 +218,5 @@ checking the disassembly of their program. # Unresolved questions [unresolved]: #unresolved-questions -None +Should we generalize this behavior in the future, so that any zero-sized type that +implements `Fn` can be converted into a `fn` pointer? From 569abdf4876c0805064636e7041be8c41caacbd1 Mon Sep 17 00:00:00 2001 From: archshift Date: Wed, 1 Feb 2017 08:10:55 -0800 Subject: [PATCH 4/4] Update coercion definition in summary --- text/0000-closure-to-fn-coercion.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-closure-to-fn-coercion.md b/text/0000-closure-to-fn-coercion.md index bec4c0accdc..5e6a9bb5f58 100644 --- a/text/0000-closure-to-fn-coercion.md +++ b/text/0000-closure-to-fn-coercion.md @@ -6,8 +6,8 @@ # Summary [summary]: #summary -A non-capturing (that is, does not `Clone` or `move` any local variables) closure -should be coercable to a function pointer (`fn`). +A closure that does not move, borrow, or otherwise access (capture) local +variables should be coercable to a function pointer (`fn`). # Motivation [motivation]: #motivation