Skip to content

Commit 2741db4

Browse files
committed
add a pair of stories about how we could have a default runtime but allow you to change back and forth
1 parent 1d150f9 commit 2741db4

File tree

2 files changed

+347
-0
lines changed

2 files changed

+347
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# ✨ Shiny future stories: template
2+
3+
[How To Vision: Shiny Future]: ../how_to_vision/shiny_future.md
4+
[the raw source from this template]: https://raw.githubusercontent.com/rust-lang/wg-async-foundations/master/src/vision/shiny_future/template.md
5+
[`shiny_future`]: https://github.com/rust-lang/wg-async-foundations/tree/master/src/vision/shiny_future
6+
[`SUMMARY.md`]: https://github.com/rust-lang/wg-async-foundations/blob/master/src/SUMMARY.md
7+
8+
## 🚧 Warning: Draft status 🚧
9+
10+
This is a draft "shiny future" story submitted as part of the brainstorming period. It is derived from what actual Rust users wish async Rust should be, and is meant to deal with some of the challenges that Async Rust programmers face today.
11+
12+
If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as peoples needs and desires for async Rust may differ greatly, shiny future stories [cannot be wrong]. At worst they are only useful for a small set of people or their problems might be better solved with alternative solutions). Alternatively, you may wish to [add your own shiny vision story][htvsq]!
13+
14+
## The story
15+
16+
Since his [early adventures](./alans_trust_in_the_compiler_is_rewarded.md) with Async I/O went so well, Alan has been looking for a way to learn more. He finds a job working in Rust. One of the projects he works on is [DistriData]. Looking at their code, he sees an annotation he has never seen before:
17+
18+
```rust
19+
#[async_runtime(humboldt)]
20+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
21+
let result = std::async_thread::spawn(async move {
22+
do_something()
23+
});
24+
}
25+
```
26+
27+
He asks Barbara, one of his coworkers, "What is this `async_runtime` annotation? And what's `humboldt`?" She answers by explaining to him that Rust's support for async I/O is actually based around an underlying runtime. "Rust gives you a pretty decent runtime by default," she says, "but it's not tuned for our workloads. We wrote our own runtime, which we call `humboldt`."
28+
29+
[DistriData]: ../projects/DistriData.md
30+
31+
Alan asks, "What happens with the various std APIs? For example, I see we are calling `std::async_thread::spawn` -- when I used that before, it spawned tasks into the default runtime. What happens now?"
32+
33+
Barbara explains that the "async" APIs in std generally execute relative to the current runtime that is in use. "When you call `std::async_thread::spawn`, it will spawn a task onto the current runtime. It's the same with the routines in `std::async_io` and and so forth. The `async_runtime` annotation just affects which runtime runs the `main` function, everything else follows from there."
34+
35+
## Learning more about Humboldt
36+
37+
Alan sees that some of the networking code that is being used in their application is creating network connections using humboldt APIs:
38+
39+
```rust
40+
use humboldt::network;
41+
```
42+
43+
He asks Barbara, "Why don't we use the `std::async_io` APIs for that?" She explains that Humbolt makes use of some custom kernel extensions that, naturally enough, aren't part of the std library. "TCP is for rubes," she says, "we are using TTCP -- Turbo TCP." Her mind wanders briefly to [Turbo Pascal] and she has a brief moment of yearning for the days when computers had a "Turbo" button that changed them from 8 MHz to 12 MHz. She snaps back into the present day. "Anyway, the `std::async_io` APIs just call into humboldt's APIs via various traits. But we can code directly against `humboldt` when we want to access the extra capabilities it offers."
44+
45+
[Turbo Pascal]: https://en.wikipedia.org/wiki/Turbo_Pascal
46+
47+
## Integrating into other event loops
48+
49+
Later on, Alan is working on a visualizer front-end that integrates with [DistriData] to give more details about their workloads. To do it, he needs to integrate with Cocoa APIs and he wants to run certain tasks on Grand Central Dispatch. He approaches Barbara and asks, "If everything is running on `humboldt`, is there a way for me to run some things on another event loop? How does that work?"
50+
51+
Barbara explains, "That's easy. You just have to use the `gcd` wrapper crate -- you can find it on `crates.io`. It implements the runtime traits for `gcd` and it has a `spawn` method. Once you spawn your task onto `gcd`, everything you run within gdb will be running in that context."
52+
53+
Alan says, "And so, if I want to get things running on `humboldt` again, I spawn a task back on `humboldt`?"
54+
55+
"Exactly," says Barbara. "Humboldt has a global event loop, so you can do that by just doing `humboldt::spawn`. You can also just use the `humboldt::io` APIs directly and so forth if you want to do I/O with humboldt."
56+
57+
Alan winds up with some code that looks like this:
58+
59+
```rust
60+
async fn do_something_on_humboldt() {
61+
gcd::spawn(async move {
62+
let foo = do_something_on_gcd();
63+
64+
let bar = humboldt::spawn(async move {
65+
do_a_little_bit_of_stuff_on_humboldt();
66+
});
67+
68+
combine(foo.await, bar.await);
69+
});
70+
}
71+
```
72+
73+
## 🤔 Frequently Asked Questions
74+
75+
### What status quo story or stories are you retelling?
76+
77+
Good question! I'm not entirely sure! I have to go looking and think about it. Maybe we'll have to write some more.
78+
79+
### What are the key points you were trying to convey with this status quo story?
80+
81+
* There is some way to seamlessly change to a different default runtime to use for `async main`.
82+
* There is no global runtime, just the current runtime.
83+
* When you are using this different runtime, you can write code that is hard-coded to it and which exposes additional capabilities.
84+
* You can integrate multiple runtimes relatively easily, and the std APIs work with whechever is the current runtime.
85+
86+
### How do you imagine the std APIs and so forth know the current runtime?
87+
88+
I was imagining that we would add fields to the `Context<'_>` struct that is supplied to each `async fn` when it runs. Users don't have direct access to this struct, but the compiler does. If the std APIs return futures, they would gain access to it that way as well. If not, we'd have to create some other mechanism.
89+
90+
### What happens for runtimes that don't support all the features that std supports?
91+
92+
That feels like a portability question. See the (yet to be written) sequel story, "Alan runs some things on WebAssembly". =)
93+
94+
### **What is [Alan] most excited about in this future? Is he disappointed by anything?**
95+
96+
Alan is excited about how easy it is to get async programs up and running, and he finds that they perform pretty well once he does so, so he's happy.
97+
98+
### **What is [Grace] most excited about in this future? Is she disappointed by anything?**
99+
100+
Grace is concerned with memory safety and being able to deploy her tricks she knows from other languages. Memory safety works fine here. In terms of tricks she knows and loves, she's happy that she can easily switch to another runtime. The default runtime is good and works well for most things, but for the [`DistriData`] project, they really need something tailored just for them. She is also happy she can use the extended APIs offered by `humboldt`.
101+
102+
### **What is [Niklaus] most excited about in this future? Is he disappointed by anything?**
103+
104+
Niklaus finds it async Rust quite accessible, for the same reasons cited as in ["Alan's Trust in the Rust Compiler is Rewarded"].
105+
106+
["Alan's Trust in the Rust Compiler is Rewarded"]: ../alans_trust_in_the_compiler_is_rewarded.md
107+
108+
### **What is [Barbara] most excited about in this future? Is she disappointed by anything?**
109+
110+
Depending on the technical details, Barbara may be a bit disappointed by the details of how std interfaces with the runtimes, as that may introduce some amount of overhead. This may not matter in practice, but it could also lead to library authors avoiding the std APIs in favor of writing generics or other mechanisms that are "zero overhead".
111+
112+
### **What [projects] benefit the most from this future?**
113+
114+
Projects like [DistriData] really benefit from being able to customize their runtime.
115+
116+
### **Are there any [projects] that are hindered by this future?**
117+
118+
We have to pay careful attention to embedded projects like [MonsterMesh]. Some of the most obvious ways to implement this future would lean on `dyn` types and perhaps boxing, and that would rule out embedded projects. Embedded runtimes like [embassy] are also the most different in their overall design and they would have the hardest time fitting into the std APIs (of course, many embedded projects are already no-std, but many of them make use of some subset of the std capabilities through the facade).
119+
120+
[MonsterMesh]: ../projects/MonsterMesh.md
121+
[embassy]: https://github.com/akiles/embassy
122+
123+
### **What are the incremental steps towards realizing this shiny future?**
124+
125+
There are a few steps required to realize this future:
126+
127+
* We have to determine the core mechanism that is used for std types to interface with the current scheduler.
128+
* Is it based on dynamic dispatch? Delayed linking? Some other tricks we have yet to invent?
129+
* Depending on the details, language changes may be required.
130+
* We have to hammer out the set of traits or other interfaces used to define the parts of a runtime (see below for some of the considerations).
131+
* We can start with easier cases and proceed to more difficult ones, however.
132+
133+
### **Does realizing this future require cooperation between many projects?**
134+
135+
Yes. We will need to collaborate to define traits that std can use to interface with each runtime, and the runtimes will need to implement those traits. This is going to be non-trivial, because we want to preserve the ability for independent runtimes to experiment, while also preserving the ability to "max and match" and re-use components. For example, it'd probably be useful to have a bunch of shared I/O infrastructure, or to have utility crates for locks, for running threadpools, and the like. On the other hand, tokio takes advantage of the fact that it owns the I/O types *and* the locks *and* the scheduler to do some [nifty tricks](https://tokio.rs/blog/2020-04-preemption) and we would want to ensure that remains an option.
136+
137+
### Is this story
138+
139+
[character]: ../characters.md
140+
[comment]: ./comment.md
141+
[status quo stories]: ./status_quo.md
142+
[Alan]: ../characters/alan.md
143+
[Grace]: ../characters/grace.md
144+
[Niklaus]: ../characters/niklaus.md
145+
[Barbara]: ../characters/barbara.md
146+
[projects]: ../projects.md
147+
[htvsq]: ../how_to_vision/shiny_future.md
148+
[cannot be wrong]: ../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
# ✨ Shiny future stories: template
2+
3+
[How To Vision: Shiny Future]: ../how_to_vision/shiny_future.md
4+
[the raw source from this template]: https://raw.githubusercontent.com/rust-lang/wg-async-foundations/master/src/vision/shiny_future/template.md
5+
[`shiny_future`]: https://github.com/rust-lang/wg-async-foundations/tree/master/src/vision/shiny_future
6+
[`SUMMARY.md`]: https://github.com/rust-lang/wg-async-foundations/blob/master/src/SUMMARY.md
7+
8+
## 🚧 Warning: Draft status 🚧
9+
10+
This is a draft "shiny future" story submitted as part of the brainstorming period. It is derived from what actual Rust users wish async Rust should be, and is meant to deal with some of the challenges that Async Rust programmers face today.
11+
12+
If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as peoples needs and desires for async Rust may differ greatly, shiny future stories [cannot be wrong]. At worst they are only useful for a small set of people or their problems might be better solved with alternative solutions). Alternatively, you may wish to [add your own shiny vision story][htvsq]!
13+
14+
## The story
15+
16+
### Trust the compiler
17+
Alan has a lot of experience in C#, but in the meantime has created some successful projects in Rust.
18+
He has dealt with his fair share of race conditions/thread safety issues during runtime in C#, but is now starting to trust that if his Rust code compiles,
19+
he won't have those annoying runtime problems to deal with.
20+
21+
This allows him to try to squeeze his programs for as much performance as he wants, because the compiler will stop him when he tries things that could result in runtime problems.
22+
After seeing the performance and the lack of runtime problems, he starts to trust the compiler more and more with each project finished.
23+
24+
He knows what he can do with external libraries, he does not need to fear concurrency issues if the library cannot be used from multiple threads, because the compiler would tell him.
25+
26+
His trust in the compiler solidifies further the more he codes in Rust.
27+
28+
### The first async project
29+
30+
Alan now starts with his first async project. He opens up the Rust book to the "Async I/O" chapter and it guides him to writing his first program. He starts by writing some synchronous code to write to the file system:
31+
32+
```rust,ignore
33+
use std::fs::File;
34+
35+
fn main() -> Result<(), Box<dyn std::error::Error>> {
36+
let mut file = File::create("a.txt")?;
37+
file.write_all(b"Hello, world!")?;
38+
Ok(())
39+
}
40+
```
41+
42+
Next, he adapts that to run in an async fashion. He starts by converting `main` into `async fn main`:
43+
44+
```rust,ignore
45+
use std::fs::File;
46+
47+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
48+
let mut file = File::create("a.txt")?;
49+
file.write_all(b"Hello, world!")?;
50+
Ok(())
51+
}
52+
```
53+
54+
The code compiles, but he gets a warning:
55+
56+
```
57+
warning: using a blocking API within an async function
58+
--> src/main.rs:4:25
59+
1 | use std::fs::File;
60+
| ------------- try changing to `std::async_io::fs::File`
61+
| ...
62+
4 | let mut file: u32 = File::create("a.txt")?;
63+
| ^^^^^^^^^^^^ blocking functions should not be used in async fn
64+
help: try importing the async version of this type
65+
--> src/main.rs:1
66+
1 | use std::async_fs::File;
67+
```
68+
69+
"Oh, right," he says, "I am supposed to use the async variants of the APIs." He applies the suggested fix in his IDE, and now his code looks like:
70+
71+
```rust
72+
use std::async_fs::File;
73+
74+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
75+
let mut file = File::create("a.txt")?;
76+
file.write_all(b"Hello, world!")?;
77+
Ok(())
78+
}
79+
```
80+
81+
His IDE recompiles instantaneously and he now sees two little squiggles, one under each `?`. Clicking on the errors, he sees:
82+
83+
```
84+
error: missing await
85+
--> src/main.rs:4:25
86+
4 | let mut file: u32 = File::create("a.txt")?;
87+
| ^ returns a future, which requires an await
88+
help: try adding an await
89+
--> src/main.rs:1
90+
4 | let mut file: u32 = File::create("a.txt").await?;
91+
```
92+
93+
He again applies the suggested fix, and his code now shows:
94+
95+
```rust
96+
use std::async_fs::File;
97+
98+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
99+
let mut file = File::create("a.txt").await?;
100+
file.write_all(b"Hello, world!").await?;
101+
Ok(())
102+
}
103+
```
104+
105+
Happily, it compiles, and when he runs it, everything works as expected. "Cool," he thinks, "this async stuff is pretty easy!"
106+
107+
### Making some web requests
108+
109+
Next, Alan decides to experiment with some simple web requests. This isn't part of the standard library, but the `fetch_rs` package is listed in the Rust book. He runs `cargo add fetch_rs` to add it to his `Cargo.toml` and then writes:
110+
111+
```rust
112+
use std::async_fs::File;
113+
use fetch_rs;
114+
115+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
116+
let mut file = File::create("a.txt")?;
117+
file.write_all(b"Hello, world!")?;
118+
119+
let body = fetch_rs::get("https://www.rust-lang.org")
120+
.await?
121+
.text()
122+
.await?;
123+
println!("{}", body);
124+
125+
Ok(())
126+
}
127+
```
128+
129+
This feels pretty easy!
130+
131+
## 🤔 Frequently Asked Questions
132+
133+
### What status quo story or stories are you retelling?
134+
135+
* [Alan started trusting the Rust compiler, but then async](../status_quo/alan_started_trusting_the_rust_compiler_but_then_async.md)
136+
137+
### What are the key points you were trying to convey with this status quo story?
138+
139+
* Getting started with async should be as automated as possible:
140+
* change `main` to an `async fn`;
141+
* use the APIs found in modules like `std::async_foo`, which should map as closely as possible to their non-async equivalents.
142+
* You should get some sort of default runtime that is decent
143+
* Lints should guide you in using async:
144+
* identifying blocking functions
145+
* identifying missing `await`
146+
* You should be able to grab libraries from the ecosystem and they should integrate with the default runtime without fuss
147+
148+
### Is there a "one size fits all" runtime in this future?
149+
150+
This particular story doesn't talk about what happens when the default runtime isn't suitable. But you may want to read its sequel, ["Alan Switches Runtimes"](./alan_switches_runtimes.md).
151+
152+
### **What is [Alan] most excited about in this future? Is he disappointed by anything?**
153+
154+
Alan is excited about how easy it is to get async programs up and running. He also finds the performance is good. He's good.
155+
156+
### **What is [Grace] most excited about in this future? Is she disappointed by anything?**
157+
158+
Grace is happy because she is getting strong safety guarantees and isn't getting surprising runtime panics when composing libraries. The question of whether she's able to use the tricks she knows and loves is a good one, though. The default scheduler may not optimize for maximum performance -- this is something to explore in future stories. The ["Alan Switches Runtimes"](./alan_switches_runtimes.md), for example, talks more about the ability to change runtimes.
159+
160+
### **What is [Niklaus] most excited about in this future? Is he disappointed by anything?**
161+
162+
Niklaus is quite happy. Async Rust is fairly familiar and usable for him. Further, the standard library includes "just enough" infrastructure to enable a vibrant crates-io ecosystem without centralizing everything.
163+
164+
### **What is [Barbara] most excited about in this future? Is she disappointed by anything?**
165+
166+
Barbara quite likes that the std APIs for sync and sync fit together, and that there is a consistent naming scheme across them. She likes that there is a flourishing ecosystem of async crates that she can choose from.
167+
168+
### **What [projects] benefit the most from this future?**
169+
170+
A number of projects benefit:
171+
172+
* Projects like [YouBuy] are able to get up and going faster.
173+
* Libraries like [SLOW] become easier because they can target the std APIs and there is a defined plan for porting across runtimes.
174+
175+
[YouBuy]: ../projects/YouBuy.md
176+
[SLOW]: ../projects/SLOW.md
177+
178+
### **Are there any [projects] that are hindered by this future?**
179+
180+
It depends on the details of how we integrate other runtimes. If we wound up with a future where most libraries are "hard-coded" to a single default runtime, this could very well hinder any number of projects, but nobody wants that.
181+
182+
### **What are the incremental steps towards realizing this shiny future?**
183+
184+
This question can't really be answered in isolation, because so much depends on the story for how we integrate with other runtimes. I don't think we can accept a future where is literally a single runtime that everyone has to use, but I wanted to pull out the question of "non-default runtimes" (as well as more details about the default) to other stories.
185+
186+
### **Does realizing this future require cooperation between many projects?**
187+
188+
Yes. For external libraries like `fetch_rs` to interoperate they will want to use the std APIs (and probably traits).
189+
190+
[character]: ../characters.md
191+
[comment]: ./comment.md
192+
[status quo stories]: ./status_quo.md
193+
[Alan]: ../characters/alan.md
194+
[Grace]: ../characters/grace.md
195+
[Niklaus]: ../characters/niklaus.md
196+
[Barbara]: ../characters/barbara.md
197+
[projects]: ../projects.md
198+
[htvsq]: ../how_to_vision/shiny_future.md
199+
[cannot be wrong]: ../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade

0 commit comments

Comments
 (0)