|
| 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