diff --git a/404.html b/404.html index c3c00ac..e1c895b 100644 --- a/404.html +++ b/404.html @@ -83,7 +83,7 @@ diff --git a/CHARTER.html b/CHARTER.html index 9cb1828..f4ef2c1 100644 --- a/CHARTER.html +++ b/CHARTER.html @@ -82,7 +82,7 @@ diff --git a/archive/evaluation/effect-hierarchy.html b/archive/evaluation/effect-hierarchy.html index 8841222..cfe7f04 100644 --- a/archive/evaluation/effect-hierarchy.html +++ b/archive/evaluation/effect-hierarchy.html @@ -82,7 +82,7 @@ diff --git a/archive/evaluation/effects-in-rust.html b/archive/evaluation/effects-in-rust.html index 77b2d3b..4e662de 100644 --- a/archive/evaluation/effects-in-rust.html +++ b/archive/evaluation/effects-in-rust.html @@ -82,7 +82,7 @@ diff --git a/archive/evaluation/effects-in-rust/async.html b/archive/evaluation/effects-in-rust/async.html index d1f5d7f..44e2158 100644 --- a/archive/evaluation/effects-in-rust/async.html +++ b/archive/evaluation/effects-in-rust/async.html @@ -82,7 +82,7 @@ diff --git a/archive/evaluation/effects-in-rust/const.html b/archive/evaluation/effects-in-rust/const.html index 06c2631..6d941d3 100644 --- a/archive/evaluation/effects-in-rust/const.html +++ b/archive/evaluation/effects-in-rust/const.html @@ -82,7 +82,7 @@ diff --git a/archive/evaluation/effects-in-rust/gen.html b/archive/evaluation/effects-in-rust/gen.html index 4e39c25..bf34c5b 100644 --- a/archive/evaluation/effects-in-rust/gen.html +++ b/archive/evaluation/effects-in-rust/gen.html @@ -82,7 +82,7 @@ diff --git a/archive/evaluation/effects-in-rust/ownership.html b/archive/evaluation/effects-in-rust/ownership.html index c2cafff..e076acf 100644 --- a/archive/evaluation/effects-in-rust/ownership.html +++ b/archive/evaluation/effects-in-rust/ownership.html @@ -82,7 +82,7 @@ diff --git a/archive/evaluation/effects-in-rust/panic.html b/archive/evaluation/effects-in-rust/panic.html index dc05f68..65e0f35 100644 --- a/archive/evaluation/effects-in-rust/panic.html +++ b/archive/evaluation/effects-in-rust/panic.html @@ -82,7 +82,7 @@ diff --git a/archive/evaluation/effects-in-rust/pin.html b/archive/evaluation/effects-in-rust/pin.html index 82f0b7d..c53aac1 100644 --- a/archive/evaluation/effects-in-rust/pin.html +++ b/archive/evaluation/effects-in-rust/pin.html @@ -82,7 +82,7 @@ diff --git a/archive/evaluation/effects-in-rust/send.html b/archive/evaluation/effects-in-rust/send.html index cda5b34..86cd38c 100644 --- a/archive/evaluation/effects-in-rust/send.html +++ b/archive/evaluation/effects-in-rust/send.html @@ -82,7 +82,7 @@ diff --git a/archive/evaluation/effects-in-rust/sized.html b/archive/evaluation/effects-in-rust/sized.html index 1b49e68..e570b86 100644 --- a/archive/evaluation/effects-in-rust/sized.html +++ b/archive/evaluation/effects-in-rust/sized.html @@ -82,7 +82,7 @@ diff --git a/archive/evaluation/effects-in-rust/try.html b/archive/evaluation/effects-in-rust/try.html index 7e837a1..865c731 100644 --- a/archive/evaluation/effects-in-rust/try.html +++ b/archive/evaluation/effects-in-rust/try.html @@ -82,7 +82,7 @@ diff --git a/archive/evaluation/effects-in-rust/unsafe.html b/archive/evaluation/effects-in-rust/unsafe.html index 05b02a5..a2fd343 100644 --- a/archive/evaluation/effects-in-rust/unsafe.html +++ b/archive/evaluation/effects-in-rust/unsafe.html @@ -82,7 +82,7 @@ diff --git a/archive/evaluation/grouping-keyword-generics.html b/archive/evaluation/grouping-keyword-generics.html index 301a47d..e57169f 100644 --- a/archive/evaluation/grouping-keyword-generics.html +++ b/archive/evaluation/grouping-keyword-generics.html @@ -82,7 +82,7 @@ diff --git a/archive/evaluation/introducing-new-keyword-generics.html b/archive/evaluation/introducing-new-keyword-generics.html index 917571f..07f8406 100644 --- a/archive/evaluation/introducing-new-keyword-generics.html +++ b/archive/evaluation/introducing-new-keyword-generics.html @@ -82,7 +82,7 @@ diff --git a/archive/evaluation/mir-desugaring.html b/archive/evaluation/mir-desugaring.html index 067fc32..62f2a00 100644 --- a/archive/evaluation/mir-desugaring.html +++ b/archive/evaluation/mir-desugaring.html @@ -82,7 +82,7 @@ diff --git a/archive/evaluation/overloading-keyword-generics.html b/archive/evaluation/overloading-keyword-generics.html index 4efee7a..d8a1273 100644 --- a/archive/evaluation/overloading-keyword-generics.html +++ b/archive/evaluation/overloading-keyword-generics.html @@ -82,7 +82,7 @@ diff --git a/archive/evaluation/prior-art.html b/archive/evaluation/prior-art.html index be63728..6fc8a74 100644 --- a/archive/evaluation/prior-art.html +++ b/archive/evaluation/prior-art.html @@ -82,7 +82,7 @@ diff --git a/archive/index.html b/archive/index.html index 691f83a..5708227 100644 --- a/archive/index.html +++ b/archive/index.html @@ -82,7 +82,7 @@ diff --git a/evaluation/auto-concurrency.html b/evaluation/auto-concurrency.html new file mode 100644 index 0000000..0f071d1 --- /dev/null +++ b/evaluation/auto-concurrency.html @@ -0,0 +1,420 @@ + + + + + + Auto Concurrency - keyword generics initiative + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+
+

Auto Concurrency

+

Async Rust brings three unique capabilities to +Rust: the ability to apply ad-hoc +concurrency, the ability to arbitrarily pause, cancel and resume operations, and +finally the ability to combine these capabilities into new ones - such as ad-hoc +timeouts. Async Rust also does one other thing: it decouples "concurrency" from +"parallelism" - while in non-async Rust both are coupled into the "thread" +primitive.

+

One challenge however is to make use of these capabilities. People notoriously +struggle to use cancellation correctly, and are often caught off guard that +computations after being suspended at an .await point may not necessarily be +resumed ("cancelled"). Similarly: users will often struggle to apply +fine-grained concurrency in their applications - because it fundamentally means +exploding sequential control-flow sequences into Directed Acyclic control-flow +Graphs (control-flow DAGs).

+

By Example: Swift

+

Swift has introduced the async let keyword to enable linear-looking +control-flow which statically expands to a concurrent DAG backed by tasks. To +see how this works we can reference +SE-0304's +example which provides a makeDinner routine:

+
func makeDinner() async throws -> Meal {
+  async let veggies = chopVegetables()                    // 1. concurrent with: 2, 3
+  async let meat = marinateMeat()                         // 2. concurrent with: 1, 3
+  async let oven = preheatOven(temperature: 350)          // 3. concurrent with: 1, 2, 4
+
+  let dish = Dish(ingredients: await [try veggies, meat]) // 4. depends on: 1, 2, concurrent with: 3
+  return await oven.cook(dish, duration: .hours(3))       // 5. depends on: 3, 4, not concurrent
+}
+
+

The following constraints and operations occur here:

+
    +
  • constraint: dish depends on veggies and meat.
  • +
  • concurrency: veggies, meat, and oven are computed concurrently
  • +
  • constraint: Meal depends on oven and dish
  • +
  • concurrency: oven and dish are computed concurrently
  • +
+

In Swift the async let syntax automatically spawns tasks and ensures that they +resolve when they need to. In Swift await {} and try {} apply not just to +the top-level expressions but also to all sub-expressions, so for example +awaiting the oven is handled by await oven.cook (..). We can translate this +to Rust using the futures-concurrency +library without having to use parallel +tasks - just concurrent futures. That would look like this:

+

+#![allow(unused)]
+fn main() {
+use futures_concurrency::prelude::*;
+
+async fn make_dinner() -> SomeResult<Meal> {
+    let dish = {
+        let veggies = chop_vegetables();
+        let meat = marinate_meat();
+        let (veggies, meat) = (veggies, meat).try_join().await?;
+        Dish::new(&[veggies, meat]).await
+    };
+    let (dish, oven) = (dish, preheat_oven(350)).try_join().await?;
+    oven.cook(dish, Duration::from_mins(3 * 60)).await
+}
+}
+
+

Compared to Swift the control-flow here is much harder to tease apart. We've +accurately described our concurrency DAG; but reversing it to understand +intent has suddenly become a lot harder. Programmers generally have a better +time understanding code when it can be read sequentially; and so it's no +surprise that the Swift version is better at stating intent.

+

Auto-concurrency for Rust's Async Effect Contexts

+

Rust's async system differs a little from Swift's, but only in the details. The +main differences as it comes to what we'd want to do here are three-fold:

+
    +
  1. Swift's async primitive are tasks: which are managed, parallel async +primitives. In Rust it's Future, which is unmanaged and not parallel by +default - it's only concurrent.
  2. +
  3. In Rust all .await points have to be explicit and recursive awaiting of +expressions is not supported. This is because as mentioned earlier: functions +may permanently yield control flow at .await points, and so they have to be +called out in the source code.
  4. +
+

For these reasons we can't quite do what Swift does - but I believe we could +probably do something similar. From a language perspective, it seems like it +should be possible to do a similar system to async let. Any number of async let statements can be joined together by the compiler into a single +control-flow graph, as long as their outputs don't depend on each other. And if +we're calling .await? on async let statements we can even ensure to insert +calls to try_join so concurrently executing functions can early abort on +error.

+

+#![allow(unused)]
+fn main() {
+async fn make_dinner() -> SomeResult<Meal> {
+    async let veggies = chop_vegetables();  // 1. concurrent with: 2, 3
+    async let meat = marinate_meat();       // 2. concurrent with: 1, 3
+    async let oven = preheat_oven(350);     // 3. concurrent with: 1, 2, 4
+
+    async let dish = Dish(&[veggies.await?, meat.await?]);   // 4. depends on: 1, 2, concurrent with: 3
+    oven.cook(dish.await, Duration::from_mins(3 * 60)).await // 5. depends on: 3, 4, not concurrent
+}
+}
+
+

Here, just like in the Swift example, we'd achieve concurrency between all +independent steps. And where steps are dependent on one another, they would be +computed as sequential. Each future still needs to be .awaited - but in order +to be evaluated concurrently the program authors no longer have to figure it out +by hand.

+

If we think about it, this feels like a natural evolution from the principles of +async/.await. Just the syntax alone provides us with the ability to convert +complex asynchronous callback graphs into seemingly imperative-looking code. And +by extending that to concurrency too, we're able to reap even more benefits from it.

+

What about other concurrency operations?

+

A brief look at the futures-concurrency +library will +reveal a number of concurrency operations. Yet here we're only discussing one: +Join. That is because all the other operations do something which is unique to +async code, and so we have to write async code to make full use of it. Whereas +join does not semantically change the code: it just takes independent +sequential operations and runs them in concert.

+

Maybe-async and auto-concurrency

+

The main premise of #[maybe(async)] notations is that they can take sequential +code and optionally run them without blocking. Under the system described in +this post that code could not only be non-blocking, it could also be concurrent. +Taking the system we're describing in the "Effect Generic Function Bodies and +Bounds" draft, we could write our async let-based code example as follows to +make it conditional over the async effect:

+

+#![allow(unused)]
+fn main() {
+#[maybe(async)]  // <- changed `async fn` to `#[maybe(async)] fn`
+fn make_dinner() -> SomeResult<Meal> {
+    async let veggies = chop_vegetables();
+    async let meat = marinate_meat();
+    async let oven = preheat_oven(350);
+
+    async let dish = Dish(&[veggies.await?, meat.await?]);
+    oven.cook(dish.await, Duration::from_mins(3 * 60)).await
+}
+}
+
+

Which when evaluated synchronously would be lowered to the following code. This +code blocks and runs sequentially, but that is the best we can do without async +Rust's ad-hoc async capabilities.

+

+#![allow(unused)]
+fn main() {
+fn make_dinner() -> SomeResult<Meal> {
+    let veggies = chop_vegetables();
+    let meat = marinate_meat();
+    let oven = preheat_oven(350);
+
+    let dish = Dish(&[veggies?, meat?]);
+    oven.cook(dish, Duration::from_mins(3 * 60))
+}
+}
+
+

This is not the only way that #[maybe(async)] code could leverage async +concurrency operations: an async version of +const_eval_select +would also work. It would, however, be by far the most convenient way of +creating parity between both contexts. As well as make async Rust code that much +easier to read.

+

A note on syntax

+

An earlier version of this document proposed using .co.await, .co_await, +just .co or some other keyword to take the place of async let to indicate a +concurrent .await can happen. The feasibility of syntax like that is not +clear; though there would likely be distinct benefits to preserving the postfix +nature of existing notations. Any further exploration of this direction should +consider alternate syntaxes to async let. In particular as concurrent +execution of for await loops is something that's also desirable, and would +likely want syntax parity with concurrent execution of futures.

+

Conclusion

+

In this document we describe a mechanism inspired by Swift's async let +primitive to author imperative-looking code which is lowered into concurrent, +unmanaged futures. Rather than needing to manually convert linear code into a +concurrent directed graph, the compiler could do that for us. Here is an example +code as we would write it today using the +Join::join +operation, compared to a high-level async let based variant which would +desugar into the same code.

+

+#![allow(unused)]
+fn main() {
+/// A manual concurrent implementation using Rust 1.76 today.
+async fn make_dinner() -> SomeResult<Meal> {
+    let dish = {
+        let veggies = chop_vegetables();
+        let meat = marinate_meat();
+        let (veggies, meat) = (veggies, meat).try_join().await?;
+        Dish::new(&[veggies, meat]).await
+    };
+    let (dish, oven) = (dish, preheat_oven(350)).try_join().await?;
+    oven.cook(dish, Duration::from_mins(3 * 60)).await
+}
+
+/// An automatic concurrent implementation using a hypothetical `async let`
+/// feature. This would desugar into equivalent code as the manual example.
+async fn make_dinner() -> SomeResult<Meal> {
+    async let veggies = chop_vegetables();  // 1. concurrent with: 2, 3
+    async let meat = marinate_meat();       // 2. concurrent with: 1, 3
+    async let oven = preheat_oven(350);     // 3. concurrent with: 1, 2, 4
+
+    async let dish = Dish(&[veggies.await?, meat.await?]);   // 4. depends on: 1, 2, concurrent with: 3
+    oven.cook(dish.await, Duration::from_mins(3 * 60)).await // 5. depends on: 3, 4, not concurrent
+}
+}
+
+

This is not the first proposal to suggest an some form of concurrent notation +for async Rust; to our knowledge that would be Conrad Ludgate in their async +let blog post. However just like in +Swift it seems to be based on the idea of managed multi-threaded tasks - not +Rust's unmanaged, lightweight futures primitive.

+

A version of this is likely possible for multi-threaded code too; ostensibly via +some kind of par keyword (par let / par for await..in). A full design is out +of scope for this post; but it should be possible to improve Rust's parallel +system in both async and non-async Rust alike (using tasks and threads +respectively).

+

References

+ + +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + diff --git a/evaluation/index.html b/evaluation/index.html index 5b317a3..7df732a 100644 --- a/evaluation/index.html +++ b/evaluation/index.html @@ -82,7 +82,7 @@ diff --git a/evaluation/pattern-types.html b/evaluation/pattern-types.html index e8e96fe..671756d 100644 --- a/evaluation/pattern-types.html +++ b/evaluation/pattern-types.html @@ -82,7 +82,7 @@ @@ -430,7 +430,7 @@

- @@ -444,7 +444,7 @@

- diff --git a/evaluation/syntax/_template.html b/evaluation/syntax/_template.html index 9510b86..6cd8694 100644 --- a/evaluation/syntax/_template.html +++ b/evaluation/syntax/_template.html @@ -82,7 +82,7 @@ diff --git a/evaluation/syntax/attributes.html b/evaluation/syntax/attributes.html index 653fd9d..c912dd1 100644 --- a/evaluation/syntax/attributes.html +++ b/evaluation/syntax/attributes.html @@ -82,7 +82,7 @@ diff --git a/evaluation/syntax/const-bool-like-effects.html b/evaluation/syntax/const-bool-like-effects.html index a4eff25..8dfe87b 100644 --- a/evaluation/syntax/const-bool-like-effects.html +++ b/evaluation/syntax/const-bool-like-effects.html @@ -82,7 +82,7 @@ diff --git a/evaluation/syntax/effect-as-a-clause.html b/evaluation/syntax/effect-as-a-clause.html index b74b750..e1a2f8a 100644 --- a/evaluation/syntax/effect-as-a-clause.html +++ b/evaluation/syntax/effect-as-a-clause.html @@ -82,7 +82,7 @@ diff --git a/evaluation/syntax/index.html b/evaluation/syntax/index.html index 6dfb06e..9b0af81 100644 --- a/evaluation/syntax/index.html +++ b/evaluation/syntax/index.html @@ -82,7 +82,7 @@ diff --git a/evaluation/syntax/postfix-question-mark.html b/evaluation/syntax/postfix-question-mark.html index c2d18d7..07a1179 100644 --- a/evaluation/syntax/postfix-question-mark.html +++ b/evaluation/syntax/postfix-question-mark.html @@ -82,7 +82,7 @@ diff --git a/evaluation/syntax/where-effect-bounds.html b/evaluation/syntax/where-effect-bounds.html index 0a3b8d2..3ef836d 100644 --- a/evaluation/syntax/where-effect-bounds.html +++ b/evaluation/syntax/where-effect-bounds.html @@ -82,7 +82,7 @@ diff --git a/explainer/effect-generic-bounds-and-functions.html b/explainer/effect-generic-bounds-and-functions.html index 605d521..f8801cc 100644 --- a/explainer/effect-generic-bounds-and-functions.html +++ b/explainer/effect-generic-bounds-and-functions.html @@ -82,7 +82,7 @@ diff --git a/explainer/effect-generic-trait-declarations.html b/explainer/effect-generic-trait-declarations.html index d84d865..3f5cab7 100644 --- a/explainer/effect-generic-trait-declarations.html +++ b/explainer/effect-generic-trait-declarations.html @@ -82,7 +82,7 @@ diff --git a/explainer/index.html b/explainer/index.html index d372a3f..db6953c 100644 --- a/explainer/index.html +++ b/explainer/index.html @@ -82,7 +82,7 @@ @@ -160,7 +160,7 @@

📚 Draft RFCs - @@ -174,7 +174,7 @@

📚 Draft RFCs