summaryrefslogtreecommitdiffstats
path: root/0000-struct-closure.md
blob: a2acb6ad728f4c44ac8dfdbbbae35a2597c3ef77 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
- Feature Name: `struct_closure`
- Start Date: 2022-08-05
- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000)
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000)

# Summary
[summary]: #summary

Concrete Closure Types

# Motivation
[motivation]: #motivation

This proposal exists entirely out of spite for the `char::is_ascii*` family of
functions.

# Guide-level explanation
[guide-level-explanation]: #guide-level-explanation

Closures in Rust are represented by the struct `core::ops::Closure`, while
callables are represented by the traits `Fn`, `FnMut` and `FnOnce`. This
enables closures to participate specially in trait resolution, and be
distinguished from other types of callables (particularly user-defined ones).

In general, when writing generic code, it is preferred to use the `Fn*` traits.
However, in some edge-cases it can be useful to use `Closure` or even both.

## Examples

The main user of the `Closure` struct is `Pattern`:

```rust
fn takes_char(x: char) -> bool {
    true
}

fn takes_char_ref(x: &char) -> bool {
    true
}

// these both work:
"hello".trim_start_matches(takes_char);
"hello".trim_start_matches(takes_char_ref);
```

This wouldn't be possible if `Pattern` were instead relying on the traits,
as it's entirely possible for a type to implement multiple `Fn` traits, but a
closure will never do so.

Due to historical reasons, `char` provides some functions that take `&self` and
some which take `self`:

```rust
char::is_ascii(&'a');
char::is_numeric('9');
```

and they both work as `Pattern`, thanks to `Closure`:

```rust
"hello".trim_start_matches(char::is_ascii);
"hello".trim_start_matches(char::is_numeric);
```

# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation

Currently the `Fn*` traits are pretty unstable, including their internal
representation. This proposal simply introduces a new, special lang item:

```rust
#[rustc_paren_sugar]
#[lang="closure"]
struct Closure<F: FnOnce<Args>, Args> {
    // compiler special...
}

impl<F, Args> Fn<Args> for Closure<F, Args> where F: Fn<Args> {
    // details omitted...
}
impl<F, Args> FnMut<Args> for Closure<F, Args> where F: FnMut<Args> {
    // details omitted...
}
impl<F, Args> FnOnce<Args> for Closure<F, Args> where F: FnOnce<Args> {
    // details omitted...
}
```

Some things to note: The details of `Args` are subject to change. This proposal
relies on that, because otherwise this cannot have the correct `for<'a>`
bounds. This proposal does not define the details of `Args`, but suggests they
might be of `fn` type. (Indeed, it would be easiest to implement this by
defining `Args` to be an `fn` type. Tho do note the `Args` in `Closure` does
not need to be the same `Args` in `Fn*`, as long as the compiler knows how to
map between them.)

In general, it'd be awkward to use this in a function. Consider:

```rust
fn foo<F: Fn...>(closure: Closure<F, ...>)
```

It's much simpler to just use F directly. But in a trait, things are different,
because you can't have multiple generic impls like that, so it actually becomes
useful:

```rust
// this DOES NOT WORK
impl<F: FnMut(char) -> bool> Pattern for F { ... }
impl<F: FnMut(&char) -> bool> Pattern for F { ... }

// but this would, because these types don't overlap
impl<F: FnMut(char) -> bool> Pattern for Closure<F, fn(char) -> bool> { ... }
impl<F: FnMut(&char) -> bool> Pattern for Closure<F, fn(&char) -> bool> { ... }
```

This is, for all intents and purposes, a "zero-cost" wrapper over
closures (except for one layer of type system nesting); and unlike `fn` types,
it doesn't incur the additional pointer overhead, being the same size as the
underlying closure.

Additionally, as this is the main goal of the RFC, we add the following impl to
`Pattern`:

```rust
impl<'a, F> Pattern<'a> for Closure<F, fn(&char) -> bool>
where
    F: FnMut(&char) -> bool,
{
    pattern_methods!(CharRefPredicateSearcher<'a, F>, MultiCharEqPattern, CharRefPredicateSearcher);    
}
```

Which, for all intents and purposes, is the same as the existing generic impl,
except with `&char` instead of `char`.

# Drawbacks
[drawbacks]: #drawbacks

- Small extra cost due to additional wrapper type. This could affect existing
    users of closures with longer compilation times, but it shouldn't affect
    runtime performance.

# Rationale and alternatives
[rationale-and-alternatives]: #rationale-and-alternatives

This proposal intentionally distinguishes closures from other kinds of
callables, solely to fix the issue with our beloved `char::is_ascii*` family of
functions. Given that the sole reason for this proposal is to fix that issue,
there's actually a huge range of possible alternatives. These are a couple of
examples:

- We might be able to `impl Pattern for fn(&char) -> bool`. This does raise the
    question of whether we *want* non-closure types which happen to be callable
    with a bare `char` and return a `bool` to also be usable as a `Pattern`, or
    only "true" closures. It also raises the question of whether we want to
    accept closures which take `&char`, or only functions. This has not been
    discussed previously. See unresolved questions.

    This proposal rejects this alternative by principle: If `Pattern` works on
    `(char) -> bool` closures (*including* `(char) -> bool` functions), and
    `(&char) -> bool` functions, it creates an inconsistency where
    `(&char) -> bool` closures don't work for seemingly no good reason.
- There have been past discussions about potential "overlapping impls" as a way
    of solving this, but they never got very far and were quite underspecified.
    This proposal is much narrower in scope, so it shouldn't have those issues.

# Prior art
[prior-art]: #prior-art

Tentatively, the way function pointers work today is the prior art.

# Unresolved questions
[unresolved-questions]: #unresolved-questions

1. This proposal raises the question of whether we want non-closure callables
    to be `Pattern`. Note that, today, it is not possible to specialize
    `Pattern` for such callables.

    This can be resolved before stabilization: this proposal allows either
    option to be chosen, e.g.:

    ```rust
    // any (char) -> bool callable
    impl<F: FnMut(char) -> bool> Pattern for F { ... }
    // only (&char) -> bool closures
    impl<F: FnMut(&char) -> bool> Pattern for Closure<F, fn(&char) -> bool> { ... }
    ```

    or even with specialization:

    ```rust
    // any (char) -> bool callable (specializable)
    impl<F: FnMut(char) -> bool> Pattern for F { ... }
    // only (char) -> bool closures
    impl<F: FnMut(char) -> bool> Pattern for Closure<F, fn(char) -> bool> { ... }
    // only (&char) -> bool closures
    impl<F: FnMut(&char) -> bool> Pattern for Closure<F, fn(&char) -> bool> { ... }
    ```
2. This proposal changes type inference for closures. Specifically, adding
    `impl<F: Fn(&char) -> bool> Pattern for Closure<F, fn(&char)>` would break
    inference for existing users of `str.matches(|x| x.whatever)`, tho this is
    not that big of a deal, as the edition machinery can handle this today, in
    a similar way to how it can handle `IntoIterator` for arrays. But the
    unresolved question is whether we want to define new stability rules for
    trait impls on closures.

    Today, Rust mostly considers new trait impls to be non-breaking, but with
    workarounds for where such impls cause major ecosystem breakage (arrays).
    Should new impls for Closure where there was once only one be considered a
    breaking, must-never-do change, such as in the case of `Pattern`, or should
    the user be responsible for annotating the closure? This should be resolved
    before merging this RFC. (Or, indeed, this RFC should be rejected if any of
    this is unacceptable - after all, the proposal sets out to solve `Pattern`
    for the `char::is_ascii*` family of functions, and this would prevent
    that.)
3. The `Args`. Just like for `Fn*`, this must be resolved before stabilization.

# Future possibilities
[future-possibilities]: #future-possibilities

N/A.