Step 1 of 7 · Reading · ~3 min
Learn
Lifetimes, Trait Objects, and Async
Async Rust lets you write code that LOOKS synchronous but yields control while waiting on I/O.
What runs here, and what does not
This course's grader compiles single files with rustc in the 2015 edition.
async and .await are not keywords in that edition, so an async fn in your
submission does not merely fail to run - it fails to compile, with
error[E0670]: async fn is not permitted in the 2015 edition. There is also no
executor available: tokio and async-std are crates, and no crates are linked.
So read the async fn snippets below as the syntax you will meet in real
projects, and expect the exercise to build a future the long way - by hand,
with the std::future::Future trait, which the 2015 edition does support.
async fn - futures
Marking a function async makes it return a Future:
async fn fetch_data() -> String {
// ... await some I/O ...
String::from("data")
}
let fut = fetch_data(); // a Future, not a String - nothing has run yet
Futures are LAZY in Rust. Calling an async fn just builds a state machine. Nothing in the body executes until something polls it.
.await - yielding for async results
Inside an async context, .await extracts the value from a future, suspending
until it is ready:
async fn build_response() -> String {
let user = fetch_user().await;
let settings = fetch_settings().await;
format!("{} - {}", user, settings)
}
.await can only appear inside an async fn or async block. The compiler
rewrites the function into a state machine that suspends at each await point.
Running futures: the executor
A future does nothing until polled, so you need a runtime:
#[tokio::main]
async fn main() {
let s = fetch_data().await;
println!("{}", s);
}
The runtime polls futures, schedules them, and parks them on I/O readiness via
the OS (epoll/kqueue/IOCP). tokio::spawn puts a future on the runtime,
tokio::join! waits for several, tokio::select! races them.
What async fn desugars to
An async fn is sugar for a type implementing Future. Written out, a future
that is immediately ready looks like this - and this version compiles under the
2015 edition with no crates:
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
struct Doubler { n: i32 }
impl Future for Doubler {
type Output = i32;
fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<i32> {
Poll::Ready(self.n * 2) // a real future would return Poll::Pending
}
}
fn double(n: i32) -> Doubler { Doubler { n: n } }
fn main() {
let _fut = double(7); // builds the future; poll is never called
println!("created future"); // prints: created future
}
Three things to read off that signature. Output is what the future resolves
to. Poll is either Ready(value) or Pending. And self arrives as
Pin<&mut Self>, not &mut Self: a compiler-generated future holds references
into its own state machine, so moving it after the first poll would leave those
references dangling. Pin is the type that forbids the move. Most code never
names Pin - it only shows up when you implement a future yourself.
Async vs threads
- Threads - each task gets its own OS stack; context switches and memory cost
- Async - many tasks multiplexed onto few threads; cheap per task, suited to I/O
A server holding 10k mostly-idle connections wants async. CPU-bound parallelism wants threads, often via rayon.
Send and Sync for futures
A future from an async fn is Send only if everything it holds ACROSS an await
point is Send. An Rc held across .await makes the whole future !Send,
which tokio::spawn then rejects. Drop the Rc before the await, or use Arc.
Common mistakes
- Building a future and never awaiting it - nothing happens; the compiler usually warns.
- Blocking inside async -
std::thread::sleepor sync I/O stalls the whole executor thread. - Holding non-Send types across .await - breaks
tokio::spawn. - Spawning async tasks for CPU work - async is for waiting, not for computing.
- Expecting
async fnto run without a runtime - there is no built-in executor, here or anywhere.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…