Without a yield instruction its strange to ask "how do I start all these futures before I await" and join does make sense because it does both of those things. But other languages can start futures, yield, reenter, start more futures, and wait for them all while making progress in the mean time.
I'm curious what the plan in there.
A future in rust is really just a trait that implements a `poll` function, whose return type is either "Pending" or "Ready<T>". When you create a future, you're just instantiating a type implementing that function.
For a future to make progress, you need to call its poll function. A totally valid (if very inefficient) strategy is to repeatedly call the Future's poll function in a loop until it returns Ready.
All the `await` keyword does is propagate the Pending case up. It can be expanded trivially to the following:
match future.poll() {
Pending => return Pending,
Ready(val) => val
}
Now, when we create a top-level Future, it won't start executing. We need to spawn it on an Executor. The Executor is generally provided by a library (tokio, async-std, others...) that gives you an event loop and various functions to perform Async IO. Those functions will generally be your leaf-level/bottommost futures, which are implemented by having the poll function register the interest in a given resource (file descriptor or what not) so that the currently executing top-level Future will be waken up when that resource has progressed by the Executor.So if you want to start a future, you will either have to spawn it as a top-level future (in which case you cannot get its output or know when it finishes executing unless you use channels) or you join it with other sub-futures so that all your different work will progress simultaneously. Note that you can join multiple time, so you can start two futures, join them, and have one of those futures also start two futures and join them, there's no problem here.
What would be the syntax for how you would spawn a future, add it to the current Executor, cooperatively yield execution in the parent such that progress could be made on the child, but also return execution to the parent if the child yields but does not complete?
In C# I believe you could simply call
Task.Run(Action);
This will schedule the action to the current executor. If you yield execution through await that task will (have a chance to) run. The crux of the question is that the fact that you do not need to await that specific task, you just need to cooperatively yield execution such that the executor is freed up. var shortTask = Task.Run(Task.Delay(100).Wait);
var longTask = Task.Run(Task.Delay(500).Wait);
await Task.Delay(1000);
await shortTask;
await longTask;
In C# this will take ~1000 milliseconds as the child futures yield execution back to the parent such that it can start its own yielding task. let future = async_std::task::spawn(timer_future());
And then do more work. The timer_future will run cooperatively, and the future returned by spawn is merely a “join handle”.But this is a feature of the executor, not something that’s part of the core rust async/await. With tokio, you’d have to spawn a separate future and use channels to get the return value.
use std::time::Duration;
use async_std::task;
let shortTask = task::spawn(task::sleep(Duration::from_secs(1)));
let longTask = task::spawn(task::sleep(Duration::from_secs(2)));
task::sleep(Duration::from_secs(5)).await;
shortTask.await;
longTask.await;
or simply: futures.join!(
task::sleep(Duration::from_secs(1),
task::sleep(Duration::from_secs(2),
task::sleep(Duration::from_secs(5)
).await;