Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Allow coercing non-capturing closures to function pointers. #1558

Merged
merged 4 commits into from
Feb 14, 2017
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Clarify re-coercion, pidgeonhole drawback; fix typos
archshift authored Nov 7, 2016
commit 1a22f6073b557a213bdd7a8067cf0a47f775d7e0
65 changes: 56 additions & 9 deletions text/0000-closure-to-fn-coercion.md
Original file line number Diff line number Diff line change
@@ -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
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This detailed design doesn't seem to mention how this fits into Rust's type checker. My expectation is that it uses the 'expected type' -- meaning that it's a kind of coercion. Basically, when we type-check a closure expression, if the expected type is a fn(), we will coerce to a fn pointer, but otherwise we will not.

This implies the usual limitations that come along with coercions (at least today). These are somewhat stronger than what you mention. For example, once a closure is assigned to a variable, it will no longer be possible to coerce it to a fn() pointer:

fn foo(x: fn());

foo(|| ..); // OK

let x = || ...;
foo(x); // ERROR

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that stronger limitation fundamental? It seems to me that we could introduce a marker trait, or some other way of knowing that a particular struct is coercible to a fn pointer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That... limitation strikes me as false. What is the difference between the type of || ... and the type of x? The coercion goes from TyClosure to TyFnPtr, doesn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I make it explicit in this RFC that it describes a coercion between the closure and fn-pointer types, rather than an implicit conversion of closure expressions to anonymous function pointers?


# 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