Skip to content

Commit 3b95d0d

Browse files
authored
Merge pull request #17 from sayaks/const-bool-like-effects
Add const-bool-like-effects syntax proposal
2 parents 419c8ee + 0835f80 commit 3b95d0d

File tree

1 file changed

+291
-0
lines changed

1 file changed

+291
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
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

Comments
 (0)