|
| 1 | +- Name: `const bool-like effects` |
| 2 | +- Proposed by: [@Lili Zoey](https://github.com/sayaks) |
| 3 | +- Original proposal: [comment](https://github.com/rust-lang/keyword-generics-initiative/issues/10#issuecomment-1445263558) |
| 4 | + |
| 5 | +# Design |
| 6 | + |
| 7 | +<!-- Please fill out the snippets labeled with "fill me in". If there are any |
| 8 | +other examples you want to show, please feel free to append more.--> |
| 9 | + |
| 10 | +## base (reference) |
| 11 | + |
| 12 | +<!-- This is the snippet which is being translated to various scenarios we're |
| 13 | +translating from. Please keep this as-is, so we can reference it later.--> |
| 14 | + |
| 15 | +```rust |
| 16 | +/// A trimmed-down version of the `std::Iterator` trait. |
| 17 | +pub trait Iterator { |
| 18 | + type Item; |
| 19 | + fn next(&mut self) -> Option<Self::Item>; |
| 20 | + fn size_hint(&self) -> (usize, Option<usize>); |
| 21 | +} |
| 22 | + |
| 23 | +/// An adaptation of `Iterator::find` to a free-function |
| 24 | +pub fn find<I, T, P>(iter: &mut I, predicate: P) -> Option<T> |
| 25 | +where |
| 26 | + I: Iterator<Item = T> + Sized, |
| 27 | + P: FnMut(&T) -> bool; |
| 28 | +``` |
| 29 | + |
| 30 | +## always async |
| 31 | + |
| 32 | +<!-- A variant where all items are always `async` --> |
| 33 | + |
| 34 | +```rust |
| 35 | +pub async trait Iterator { |
| 36 | + type Item; |
| 37 | + // function assumed async since trait is |
| 38 | + fn next(&mut self) -> Option<Self::Item>; |
| 39 | + !async fn size_hint(&self) -> (usize, Option<usize>); |
| 40 | +} |
| 41 | +// or |
| 42 | +pub trait Iterator<effect async> { |
| 43 | + type Item; |
| 44 | + fn next(&mut self) -> Option<Self::Item>; |
| 45 | + fn size_hint<effect !async>(&self) -> (usize, Option<usize>); |
| 46 | +} |
| 47 | +// or |
| 48 | +pub trait Iterator where effect async { |
| 49 | + type Item; |
| 50 | + fn next(&mut self) -> Option<Self::Item>; |
| 51 | + fn size_hint(&self) -> (usize, Option<usize>) where effect !async; |
| 52 | +} |
| 53 | + |
| 54 | + |
| 55 | +pub async fn find<I, T, P>(iter: &mut I, predicate: P) -> Option<T> |
| 56 | +where |
| 57 | + I: Iterator<Item = T> + Sized, |
| 58 | + P: async FnMut(&T) -> bool; |
| 59 | + |
| 60 | +// or |
| 61 | + |
| 62 | +pub fn find<I, T, P, effect async>(iter: &mut I, predicate: P) -> Option<T> |
| 63 | +where |
| 64 | + I: Iterator<Item = T> + Sized, |
| 65 | + P: FnMut<effect async>(&T) -> bool; |
| 66 | +``` |
| 67 | + |
| 68 | +## maybe async |
| 69 | + |
| 70 | +<!-- A variant where all items are generic over `async` --> |
| 71 | + |
| 72 | +```rust |
| 73 | +pub trait Iterator<effect A: async> { |
| 74 | + type Item; |
| 75 | + // `<effect async = A>` elided |
| 76 | + fn next(&mut self) -> Option<Self::Item>; |
| 77 | + !async fn size_hint(&self) -> (usize, Option<usize>); |
| 78 | + // or |
| 79 | + fn size_hint<effect !async>(&self) -> (usize, Option<usize>); |
| 80 | + // or |
| 81 | + fn size_hint(&self) -> (usize, Option<usize>) where effect !async; |
| 82 | + // as opposed to `where A = !async` which would make this function |
| 83 | + // only exist if we're in a context where `Iterator<A = true>` |
| 84 | +} |
| 85 | + |
| 86 | +pub fn find<I, T, P, effect A: async>(iter: &mut I, predicate: P) -> Option<T> |
| 87 | +where |
| 88 | + I: Iterator<Item = T, effect async = A> + Sized, |
| 89 | + P: FnMut<effect async = A>(&T) -> bool; |
| 90 | +``` |
| 91 | + |
| 92 | +## generic over all modifier keywords |
| 93 | + |
| 94 | +<!-- A variant where all items are generic over all modifier keywords (e.g. |
| 95 | +`async`, `const`, `gen`, etc.) --> |
| 96 | + |
| 97 | +```rust |
| 98 | +pub trait Iterator<effect A: for<effect>> { |
| 99 | + type Item; |
| 100 | + fn next(&mut self) -> Option<Self::Item>; |
| 101 | + !async fn size_hint(&self) -> (usize, Option<usize>); |
| 102 | +} |
| 103 | + |
| 104 | +pub fn find<I, T, P, effect A: for<effect>>(iter: &mut I, predicate: P) -> Option<T> |
| 105 | +where |
| 106 | + I: Iterator<Item = T, for<effect> = A> + Sized, |
| 107 | + P: FnMut<for<effect> = A>(&T) -> bool; |
| 108 | +``` |
| 109 | +[See also](#foreffect-bounds-and-traits) |
| 110 | +# Notes |
| 111 | +`!async fn foo` could be `sync fn foo` or omitted entirely in favor of only having `fn foo<effect !async>`. It is also a question if *all* effects should allow for `effect fn foo` syntax. |
| 112 | + |
| 113 | +`for<effect>` should maybe be made more special-looking since it behaves quite differently from other generic effect variables. |
| 114 | + |
| 115 | +The exact syntax of `effect A: E` and `effect E = A` for declaring a generic and specifying a bound for an effect could maybe be made different. |
| 116 | + |
| 117 | +It might be easier to implement specialization for specifically effect-generics, as they are rather simple, effectively just being bools, and there not being any lifetime parameters on them. |
| 118 | + |
| 119 | +## Some nice things about the syntax |
| 120 | + |
| 121 | +### Specific behavior |
| 122 | +To make a function have specific behavior in the case where an effect is or is not true, we could do this: |
| 123 | +```rs |
| 124 | +fn foo<effect A: async>() { |
| 125 | + if A { |
| 126 | + // do stuff when foo is async |
| 127 | + } else { |
| 128 | + // do stuff when foo is not async |
| 129 | + } |
| 130 | +} |
| 131 | +``` |
| 132 | + |
| 133 | +### Impl blocks |
| 134 | +impl blocks could look very similar to any other generics. |
| 135 | +```rust |
| 136 | +impl<effect A: async> SomeTrait<effect async = A> MyGenericType { ... } |
| 137 | +impl SomeTrait<effect async> MyAsyncType { ... } |
| 138 | +impl SomeTrait<effect !async> MySyncType { ... } |
| 139 | +``` |
| 140 | + |
| 141 | +## Description |
| 142 | +We can add effects to generics like `<effect A: E>`, and create bounds for the effects of types by doing `effect E = A` in the `<..>` list or the where-clause. |
| 143 | + |
| 144 | +The basic syntax is that `effect async = true` means the type is async, whereas `effect async = false` means it is not. |
| 145 | + |
| 146 | +For convenience we'd let `effect async` be the same as `effect async = true` and `effect !async` be the same as `effect async = false`. |
| 147 | + |
| 148 | +`async fn foo` would be syntactic sugar for `fn foo<effect async = true>`. and similar for other effects. |
| 149 | + |
| 150 | +So as an example, here are some equivalent ways of writing an async function: |
| 151 | +```rust |
| 152 | +fn foo<T, O, const N: usize, effect async = true>(...) {...} |
| 153 | +fn foo<T, O, const N: usize, effect async>(...) {...} |
| 154 | +async fn foo<T, O, const N: usize>(...) {...} |
| 155 | +fn foo<T, O, const N: usize>(...) where effect async {...} |
| 156 | +``` |
| 157 | + |
| 158 | +Every effect has a default value, and if there is no bound on the type for that specific effect it is assumed to have its default value. So the function above, having no bound on `const`, would be assumed not-const. |
| 159 | + |
| 160 | +This could be explicitly stated like |
| 161 | +```rust |
| 162 | +async fn foo<T, O, const N: usize>(...) where effect !const {...} |
| 163 | +``` |
| 164 | +However this would be unneccesary. |
| 165 | + |
| 166 | +If a type has only one generic for an effect, and no other bounds for that effect. It is assumed to have the same bound as that one generic. Meaning the following are equivalent ways of making a function generic over `async`. |
| 167 | +```rust |
| 168 | +fn foo<T, O, const N: usize, effect A: async>foo(...) where effect async = A {...} |
| 169 | +fn foo<T, O, const N: usize, effect A: async>foo(...) {...} |
| 170 | +``` |
| 171 | + |
| 172 | +However if there are multiple generics, we'd need to explicitly state what the bound should be for the type itself. |
| 173 | +```rust |
| 174 | +fn foo<T, O, const N: usize, effect A: async, effect B: async>foo(...) where effect async = A | B {...} |
| 175 | +``` |
| 176 | +This would mean that `foo` is async if either `A` is true or `B` is true. We could also use `A + B` if wanted it to be async whenever both are true. |
| 177 | + |
| 178 | +Declaring an type to have/not have an effect different from the default value might change the type. For instance |
| 179 | +`fn foo<effect async>() -> T` would become `foo() -> Future<Output = T>`. |
| 180 | + |
| 181 | +Every generic effect variable (except `for<effect>`) is also like a constant boolean value, which is true whenever the type is in a context where it has that effect, and false otherwise. |
| 182 | + |
| 183 | +In traits, the items are assumed to have the same effect bounds as the trait itself. But this can be overridden using specific bounds for that item. |
| 184 | + |
| 185 | +For instance |
| 186 | +```rs |
| 187 | +trait Read<effect A: async> { |
| 188 | + // This function is now generic over async |
| 189 | + fn read(&mut self, buf: &mut [u8]) -> Result<usize>; |
| 190 | + // or equivalently |
| 191 | + fn read(&mut self, buf: &mut [u8]) -> Result<usize> where effect async = A; |
| 192 | + |
| 193 | + // This function is now always async |
| 194 | + async fn read(&mut self, buf: &mut [u8]) -> Result<usize>; |
| 195 | + // or equivalently |
| 196 | + fn read(&mut self, buf: &mut [u8]) -> Result<usize> where effect async; |
| 197 | + |
| 198 | + // This function now only exists when the trait is async |
| 199 | + fn read(&mut self, buf: &mut [u8]) -> Result<usize> where A; |
| 200 | +} |
| 201 | +``` |
| 202 | + |
| 203 | +This also shows that unlike normal `const _: bool` we can actually use whether the generic effects are `true`/`false` in the where-clause. |
| 204 | + |
| 205 | +### `for<effect>` |
| 206 | + |
| 207 | +`for<effect>` is a universal effect bound that allows you to place bounds on all the effects of a type. Adding a `effect A: for<effect>` makes `A` a generic variable that ranges over every effect. This means its value is no longer a simple `true`/`false` and so can't be used bare in where-clauses. |
| 208 | + |
| 209 | +If another bound is added that is more specific, that bound will limit the possible values of `A` as well. Meaning that if you have `<effect A: for<effect>, effect async>`, we would have the type be generic over every effect except async. And the type would always be async. |
| 210 | + |
| 211 | +For instance, to make a function generic over all effects except const we'd write |
| 212 | +```rust |
| 213 | +fn foo<effect A: for<effect>>(...) where effect async {...} |
| 214 | +``` |
| 215 | + |
| 216 | +To place bounds on every effect we write `for<effect> = A` where `A` is some bound. This should probably be limited somewhat to avoid people writing code that can very easily break. Consider for instance `for<effect> = true`, which would declare something as having *every* effect. This could lead to breakage if a new effect is added and the function isn't compatible with this new effect. The main uses of placing bounds on `for<effect>` would to use it with other universal bounds. |
| 217 | + |
| 218 | +Using `A + B` and `A | B` bounds for universal bounds may also be problematic, as it may not always be possible to create any meaningful code that is generic in all those cases. So we may have to either disallow having multiple generic universal bounds, or have the compiler automatically infer the relationship between effects. |
| 219 | + |
| 220 | +For instance |
| 221 | +```rust |
| 222 | +fn foo<O, F1, F2, effect A: for<effect>, effect B: for<effect>>(closure1: F1, closure2: F2) -> O |
| 223 | +where |
| 224 | + F1: FnMut<for<effect> = A>() -> O, |
| 225 | + F2: FnMut<for<effect> = B>() -> O |
| 226 | +{ ... } |
| 227 | +``` |
| 228 | +Here it is unclear when `foo` should be async and const. For instance, usually a function is `async` if there is *any* async code in the function. Whereas it is `const` if *all* the code is `const`. |
| 229 | + |
| 230 | +I'm not entirely sure if this is best left up to the compiler to infer, it should be disallowed, or if the user must specify the bounds on every specific effect they may use. |
| 231 | + |
| 232 | +However if the compiler infers it all, we could still specify specific relationships, like: |
| 233 | +```rust |
| 234 | +fn foo<O, F1, F2, effect A: for<effect>, effect B: for<effect>>(closure1: F1, closure2: F2) -> O |
| 235 | +where |
| 236 | + effect async = A + B, |
| 237 | + F1: FnMut<for<effect> = A>() -> O, |
| 238 | + F2: FnMut<for<effect> = B>() -> O |
| 239 | +{ ... } |
| 240 | +``` |
| 241 | +To make this function async only if *both* `A` and `B` are async (or rather `async = true` in both sets `A` and `B`). |
| 242 | + |
| 243 | +### semi-formal description |
| 244 | +<details> |
| 245 | +<summary>Syntax</summary> |
| 246 | +There's a new kind of generic called effect-generics. For any given type, that effect may be `true` meaning the type has that effect, or it can be `false` meaning the type does not have that effect. |
| 247 | + |
| 248 | +We can make a type generic over an effect by adding `effect A: E`, where `A` is a generic variable and `E` is an effect. |
| 249 | + |
| 250 | +An effect bound is one of: `true`, `false`, `default`, `A`, `B1 + B2`, `B1 | B2`, `!B1`. Where `A` is a generic variable, `B1` and `B2` are effect bounds. |
| 251 | + |
| 252 | +An effect is either: the name of an effect, a generic variable, or `for<effect>` |
| 253 | + |
| 254 | +To specify that a type must fit some effect bound we write `effect E = A`, where `E` is an effect and `A` is an effect bound, either in the `<..>` list or in the where-clause. |
| 255 | +</details> |
| 256 | + |
| 257 | +<details> |
| 258 | +<summary>Semantics</summary> |
| 259 | + |
| 260 | +- `effect E = true` means "has the effect `E`" |
| 261 | +- `effect E = false` means "does not have the effect `E`" |
| 262 | +- `effect E = default` means "has the effect `E` if the default for the effect is true" |
| 263 | +- `effect E = A` where `A` is a generic variable, means "has the effect `E` if `A` is true" |
| 264 | +- `effect E = B1 + B2` means "has the effect `E` if the bounds `B1` and `B2` are true" |
| 265 | +- `effect E = B1 | B2` means "has the effect `E` if the bounds `B1` or `B2` are true" |
| 266 | +- `effect E = !B` means "has the effect `E` if the bound `B` is false" |
| 267 | +- `effect for<effect> = B` means "the effect bound `B` applies to every effect" |
| 268 | +- `effect A: E` means "`A` is a generic variable corresponding to the effect `E`" |
| 269 | + |
| 270 | +</details> |
| 271 | + |
| 272 | +## `for<effect>` bounds and traits |
| 273 | +In the [generic over all keywords](#generic-over-all-modifier-keywords) case we'd have that `size_hint` is generic over all effects except async. So it might be better to make such universal bounds not automatically apply to all items in a trait. |
| 274 | + |
| 275 | +In that case we'd have |
| 276 | +```rust |
| 277 | +pub trait Iterator<effect A: for<effect>> { |
| 278 | + type Item; |
| 279 | + fn next(&mut self) -> Option<Self::Item> where for<effect> = A; |
| 280 | + fn size_hint(&self) -> (usize, Option<usize>); |
| 281 | +} |
| 282 | +``` |
| 283 | +Alternatively we could have an opt-out syntax, which would look something like |
| 284 | +```rust |
| 285 | +pub trait Iterator<effect A: for<effect>> { |
| 286 | + type Item; |
| 287 | + fn next(&mut self) -> Option<Self::Item>; |
| 288 | + fn size_hint(&self) -> (usize, Option<usize>) where for<effect> = default; |
| 289 | +} |
| 290 | +``` |
| 291 | + |
0 commit comments