First Async Function
Threads let you run work on more than one CPU. Async lets you keep thousands of half-finished operations in flight on very few threads. Both are concurrency, but they solve different problems: threads are for CPU work, async is for waiting.
Marking a function async changes what it returns:
async fn greet(name: &str) -> String {
format!("Hello, {name}!")
}The signature says -> String, but the compiler rewrites it to return impl Future<Output = String>. You get the String back by awaiting it:
let greeting = greet("Ada").await;Futures are lazy
This is the part that surprises people coming from other languages. A JavaScript promise starts running the moment you create it. A Rust future does nothing at all until something polls it:
let fut = greet("Ada"); // no work has happened
let s = fut.await; // now the body runsDrop the future without awaiting it and the body never runs. There is no background thread, no scheduler entry, nothing. A future is a plain value describing work that has not started.
Something has to poll it
Because futures are inert, .await only works inside another async context, and at the top there has to be a runtime that does the polling. In tests that runtime comes from a macro:
#[tokio::test]
async fn it_greets() {
assert_eq!(greet("Ada").await, "Hello, Ada!");
}#[tokio::test] replaces #[test], builds a small runtime, and drives your async test body to completion. For a binary the equivalent is #[tokio::main] on main.
Your Task
Write two async functions.
Implement greet
pub async fn greet(name: &str) -> StringReturn "Hello, {name}!".
Implement greet_and_count
pub async fn greet_and_count(
calls: &AtomicUsize,
name: &str,
) -> StringAdd one to calls, then return the result of awaiting greet(name).
The counter is there so the tests can prove laziness. Building the future must leave calls at zero; only awaiting it may bump the count.
let calls = AtomicUsize::new(0);
let fut = greet_and_count(&calls, "Ada");
assert_eq!(calls.load(Ordering::SeqCst), 0);
assert_eq!(fut.await, "Hello, Ada!");
assert_eq!(calls.load(Ordering::SeqCst), 1);Notes
- You do not need to write
impl Futureanywhere.async fndoes it for you. greet_and_countshould callgreet, not rebuild the string itself.- Use
Ordering::SeqCstfor the counter unless you have a reason not to.
Hints
Click here to reveal hints
format!("Hello, {name}!")is the whole body ofgreet.calls.fetch_add(1, Ordering::SeqCst);adds one and returns the old value, which you can ignore.- Do not forget the
.awaitongreet(name), or you will try to return a future where aStringis expected. - Laziness is free here: it comes from
async fn, so if you write the body normally the tests pass.
Run to execute your code and see test results here.