Skip to content

Commit c8cb39f

Browse files
committed
Merge remote-tracking branch 'nikomatsakis/fn-return-assoc-type'
2 parents 2a7a5fd + d60d776 commit c8cb39f

File tree

1 file changed

+184
-0
lines changed

1 file changed

+184
-0
lines changed
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
- Start Date: (fill me in with today's date, YYYY-MM-DD)
2+
- RFC PR #: (leave this empty)
3+
- Rust Issue #: (leave this empty)
4+
5+
# Summary
6+
7+
The `Fn` traits should be modified to make the return type an associated type.
8+
9+
# Motivation
10+
11+
The strongest reason is because it would permit impls like the following
12+
(example from @alexcrichton):
13+
14+
```rust
15+
impl<R,F> Foo for F : FnMut() -> R { ... }
16+
```
17+
18+
This impl is currently illegal because the parameter `R` is not
19+
constrained. (This also has an impact on my attempts to add variance,
20+
which would require a "phantom data" annotation for `R` for the same
21+
reason; but that RFC is not quite ready yet.)
22+
23+
Another related reason is that it often permits fewer type parameters.
24+
Rather than having a distinct type parameter for the return type, the
25+
associated type projection `F::Output` can be used. Consider the standard
26+
library `Map` type:
27+
28+
```rust
29+
struct Map<A,B,I,F>
30+
where I : Iterator<Item=A>,
31+
F : FnMut(A) -> B,
32+
{
33+
...
34+
}
35+
36+
impl<A,B,I,F> Iterator for Map<A,B,I,F>
37+
where I : Iterator<Item=A>,
38+
F : FnMut(A) -> B,
39+
{
40+
type Item = B;
41+
...
42+
}
43+
```
44+
45+
This type could be equivalently written:
46+
47+
```rust
48+
struct Map<I,F>
49+
where I : Iterator, F : FnMut<(I::Item,)>
50+
{
51+
...
52+
}
53+
54+
impl<I,F> Iterator for Map<I,F>,
55+
where I : Iterator,
56+
F : FnMut<(I::Item,)>,
57+
{
58+
type Item = F::Output;
59+
...
60+
}
61+
```
62+
63+
This example highlights one subtle point about the `()` notation,
64+
which is covered below.
65+
66+
# Detailed design
67+
68+
The design has been implemented. You can see it in [this pull
69+
request]. The `Fn` trait is modified to read as follows:
70+
71+
```rust
72+
trait Fn<A> {
73+
type Output;
74+
fn call(&self, args: A) -> Self::Output;
75+
}
76+
```
77+
78+
The other traits are modified in an analogous fashion.
79+
80+
[this pull request]: https://github.com/rust-lang/rust/pull/21019
81+
82+
### Parentheses notation
83+
84+
The shorthand `Foo(...)` expands to `Foo<(...), Output=()>`. The
85+
shorthand `Foo(..) -> B` expands to `Foo<(...), Output=B>`. This
86+
implies that if you use the parenthetical notation, you must supply a
87+
return type (which could be a new type parameter). If you would prefer
88+
to leave the return type unspecified, you must use angle-bracket
89+
notation. (Note that using angle-bracket notation with the `Fn` traits
90+
is currently feature-gated, as [described here][18875].)
91+
92+
[18875]: https://github.com/rust-lang/rust/issues/18875
93+
94+
This can be seen in the In the `Map` example from the
95+
introduction. There the `<>` notation was used so that `F::Output` is
96+
left unbound:
97+
98+
```rust
99+
struct Map<I,F>
100+
where I : Iterator, F : FnMut<(I::Item,)>
101+
```
102+
103+
An alternative would be to retain the type parameter `B`:
104+
105+
```rust
106+
struct Map<B,I,F>
107+
where I : Iterator, F : FnMut(I::Item) -> B
108+
```
109+
110+
Or to remove the bound on `F` from the type definition and use it only in the impl:
111+
112+
```rust
113+
struct Map<I,F>
114+
where I : Iterator
115+
{
116+
...
117+
}
118+
119+
impl<B,I,F> Iterator for Map<I,F>,
120+
where I : Iterator,
121+
F : FnMut(I::Item) -> B
122+
{
123+
type Item = F::Output;
124+
...
125+
}
126+
```
127+
128+
Note that this final option is not legal without this change, because
129+
the type parameter `B` on the impl woudl be unconstrained.
130+
131+
# Drawbacks
132+
133+
### Cannot overload based on return type alone
134+
135+
This change means that you cannot overload indexing to "model" a trait
136+
like `Default`:
137+
138+
```rust
139+
trait Default {
140+
fn default() -> Self;
141+
}
142+
```
143+
144+
That is, I can't do something like the following:
145+
146+
```rust
147+
struct Defaulty;
148+
impl<T:Default> Fn<()> for Defaulty {
149+
type Output = T;
150+
151+
fn call(&self) -> T {
152+
Default::default()
153+
}
154+
}
155+
```
156+
157+
This is not possible because the impl type parameter `T` is not constrained.
158+
159+
This does not seem like a particularly strong limitation. Overloaded
160+
call notation is already less general than full traits in various ways
161+
(for example, it lacks the ability to define a closure that always
162+
panics; that is, the `!` notation is not a type and hence something
163+
like `FnMut() -> !` is not legal). The ability to overload based on return type
164+
is not removed, it is simply not something you can model using overloaded operators.
165+
166+
# Alternatives
167+
168+
### Special syntax to represent the lack of an `Output` binding
169+
170+
Rather than having people use angle-brackets to omit the `Output`
171+
binding, we could introduce some special syntax for this purpose. For
172+
example, `FnMut() -> ?` could desugar to `FnMut<()>` (whereas
173+
`FnMut()` alone desugars to `FnMut<(), Output=()>`). The first
174+
suggestion that is commonly made is `FnMut() -> _`, but that has an
175+
existing meaning in a function context (where `_` represents a fresh
176+
type variable).
177+
178+
### Change meaning of `FnMut()` to not bind the output
179+
180+
We could make `FnMut()` desugar to `FnMut<()>`, and hence require an
181+
explicit `FnMut() -> ()` to bind the return type to unit. This feels
182+
suprising and inconsistent.
183+
184+

0 commit comments

Comments
 (0)