Skip to content

Commit 2721c8e

Browse files
committed
Refine language principles based on comments
Also call out some of the library/ecosystem concerns.
1 parent 7636de4 commit 2721c8e

File tree

1 file changed

+53
-10
lines changed

1 file changed

+53
-10
lines changed

src/vision/roadmap/polish/sync_and_async.md

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,40 +30,83 @@ We will look at each of these in more detail, and then lay out some ideas for au
3030

3131
### Language and Semantics
3232

33-
Roughly want we want here is that code that differs only in its syncness should do the same thing.
34-
Of course, this is not strictly possible because a sync and async program are fundamentally different program.
33+
Roughly what we want here is that code that differs only in its syncness should do the same thing.
34+
Of course, this is not strictly possible because a sync and async program are fundamentally different programs.
3535
We still want something approximating this.
3636
Below are several principles that try to make this more precise.
3737
For each one, we are talking about a synchronous and asynchronous version of a piece of code where the synchronous version is basically the async version with all the `async` and `.await` keywords removed.
38+
Or conversely, we can view the async version as the sync version where all `fn`s have been replaced with `async fn` and all calls have `.await` added.
3839
Note that this assumes there are no manually implemented futures.
40+
This is an intentionally restrictive subset to focus on the core language semantics.
41+
In the Library and Ecosystem section, we will discuss replacing standard library functionality with async equivalents to make this comparison more interesting.
3942

4043
1. **Equality**: if the sync version and the async version both produce a value, then the values are the same.
41-
2. **Termination**: either both the sync and async version terminate (or can be polled to completion in the async case), or both do not terminate.
42-
3. **Panic**: the sync version panics if and only if the async version panics.
43-
4. **Types***: if the sync version has type `T` then the async version has type `Future<Output = T>` and vice-versa.
44-
5. **Compilation***: either both the sync and async version compile successfully, or they both produce equivalent compiler errors on the same line.
44+
2. **Effects**: the same set of observable effects happen in both the sync and async version, and the effects happen in the same order, at least where order is specified. Effects here includes things such as writing to a file (although realistically the async version should use the async version of the File I/O API), observable memory reads or writes.
45+
3. **Termination**: either both the sync and async version terminate (or can be polled to completion in the async case), or both do not terminate. Note that this is a special case of **Effects**.
46+
4. **Panic**: the sync version panics if and only if the async version panics. Note that this is a special case of **Effects**.
47+
5. **Types***: if the sync version of a function returns type `T` then the async version returns type `Future<Output = T>` and vice-versa. Functions or closures passed as parameters would undergo a similar transformation.
48+
6. **Compilation***: either both the sync and async version compile successfully, or they both produce equivalent compiler errors on the same line.
4549

46-
The first three principles are probably not terrible hard to achieve.
50+
The first four principles are probably not terrible hard to achieve.
4751
The last two, marked with an asterisk, may not be completely possible or even desirable in all cases.
4852

4953
For types, there is a fundamental difference in the async code because `.await` points expose types that would be purely internal in the sync version.
5054
One impact of this is that the auto traits may not be the same between the two.
5155
We might be able to get this property in one direction though.
52-
For example, adding a `.await` might make future not `Send`, but removing a `.await` will probably not remove any auto traits.
56+
For example, adding a `.await` might make the future not `Send`, but removing a `.await` will probably not remove any auto traits.
57+
See the following code for more detail:
58+
59+
```rust
60+
fn sync_foo() {
61+
let t = NonSend { ... };
62+
bar(); // `sync_foo` is `Send` with or without this line.
63+
}
64+
65+
async fn async_foo() {
66+
let t = NonSend { ... };
67+
bar().await; // With this line, the future returned by `async_foo` is `!Send`
68+
// because NonSend is `!Send` and is alive across the `.await`
69+
// point. Without this line, the future returned by `async_foo`
70+
// is `Send`.
71+
}
72+
```
73+
74+
The key difference between the sync version and the async version here is that the suspension introduced by the `.await` point reveals internal details of `async_foo` that are not observable in the `sync_foo` case.
5375

5476
Compilation is closely related to the types goal because if async causes the types to change then this could introduce or remove compilation errors.
5577
Additionally, we will probably have some async-only diagnostics, such as the [`must_not_suspend` lint][must_not_suspend].
5678

5779
### Library and Ecosystem
5880

5981
At a high level, the library and ecosystem goals are about having comparable capabilities available in libraries for both sync and async code.
60-
For example, mutexes in an async context need integration with the runtime, so the standard synchronous mutex is not generally suitable for async code.
82+
For example, mutexes in an async context need integration with the runtime, so the standard synchronous mutex is not generally suitable for async code, although there are cases where a sync mutex makes sense [[1]], [[2]].
6183
For this reason, most async runtimes provide some form of `AsyncMutex`.
6284

63-
Note that one way to achieve this may be through [Async Overloading].
85+
[1]: https://ryhl.io/blog/async-what-is-blocking/
86+
[2]: https://www.oreilly.com/library/view/programming-rust-2nd/9781492052586/ch20.html#the-group-table-synchronous-mutexes
87+
88+
Note that one way to achieve a comparable sync and async library ecosystem may be through [Async Overloading].
89+
90+
One way to approach this is to generalize the mostly mechanical transformation we described above to also include translating library calls, and then define what properties we would want to be preserved during the translation.
91+
We would assume for synchronous blocking APIs, such as File I/O, the `Read` and `Write` traits, etc., we have corresponding async File I/O APIs, `AsyncRead` and `AsyncWrite` traits, etc.
92+
The [async-std] project showed that most of the Rust standard library can be pretty directly translated into async code, other than cases where there were missing language features such as [async drop], [async traits], and [async closures].
93+
94+
95+
[async-std]: https://async.rs/
96+
[async-traits]: https://rust-lang.github.io/async-fundamentals-initiative/roadmap.html
97+
[async drop]: https://rust-lang.github.io/async-fundamentals-initiative/roadmap/async_drop.html
98+
[async-closures]: https://rust-lang.github.io/async-fundamentals-initiative/roadmap/async_closures.html
99+
64100

65101
🛠️ This area is still underdeveloped, so if you would like to help this would be a great place to pitch in!
66102

103+
#### Open, Related Questions
104+
105+
- Should `async::iter::once` take `Future<Output = T>` or `T`?
106+
- Similarly for `async::iter::empty`
107+
- And `async::iter::repeat` (one future to completion and yield return value repeatedly)
108+
- `async::iter::repeat_with` would almost certainly want to take an async closure
109+
67110
### Automated Testing
68111

69112
🛠️ This area is still underdeveloped, so if you would like to help this would be a great place to pitch in!

0 commit comments

Comments
 (0)