33
Lazy async operations in Rust
JavaScript developers may feel at home with Rust's concurrency model, which uses the async
/await
concept. In JavaScript we have Promise
to represent eventual results of async operations, and in Rust we have Future
.
The following shows an example of async
and await
syntax in JavaScript and Rust code:
JavaScript
// greet.js
async function main() {
await greet("Hi there!");
}
async function greet(message) {
console.log(message);
}
/*
Output:
Hi there!
*/
Rust
// greet.rs
#[tokio::main]
async fn main() {
greet("Hi there!").await;
}
async fn greet(message: &str) {
println!("{}", message);
}
/*
Output:
Hi there!
*/
Currently, Rust does not include an async runtime out-of-the-box. We use the Tokio runtime in our examples.
Now, consider a scenario where we want to perform an async side-effect, such as dispatching a message, but we do not want to suspend the running function until this side-effect completes.
In JavaScript we can call the publish
function, which returns a Promise, without needed to await
it. setTimeout
queues the given callback on the event loop and immediately returns a Promise
. The main
function then logs "Don't block me!" to the console. Finally the event loop eventually runs our callback, which logs "Executed main()".
// publish.js
async function main() {
publish("Executed main()"); // no await
console.log("Don't block me!");
}
// returns a Promise
function publish(message) {
return setTimeout(() => {
console.log(message);
}, 0);
}
/*
Output:
Don't block me!
Executed main()
*/
Things look different in Rust when we do not await
the Future
returned by publish
.
// publish.rs
#[tokio::main]
#[allow(unused_must_use)]
async fn main() {
publish("Executed main()"); // no await
println!("Don't block me!");
}
async fn publish(message: &str) {
println!("{}", message);
}
/*
Output:
Don't block me!
*/
For some reason, the Rust program output does not show "Executed main()"! According to the Rust documentation on Future
, "futures do nothing unless you .await
them". Unlike JavaScript, Rust's async operations are lazy.
In Rust we must use .await
on the Future
to execute the async operation. However awaiting the publish
causes the main
function to suspend and prevent execution of println!("Don't block me!")
until after publish
completes. To avoid this situation and achieve a similar behaviour to JavaScript's Promise handling, we can use tokio::spawn
.
// publish.rs
#[tokio::main]
async fn main() {
// Spawns a new async task, and executes it concurrently
tokio::spawn(async {
publish("Executed main()").await;
});
println!("Don't block me!");
}
async fn publish(message: &str) {
println!("{}", message);
}
/*
Output:
Don't block me!
Executed main()
*/
It works as we intended! tokio::spawn
executes publish
concurrently without blocking the remaining lines operations in main
!
33