Why can't a normal function call an async one?¶
Level: 201 · anyone who added async to one function and had to add it to ten
Stub — a question and its expected answers, not yet a lesson. No program behind this page has been written or run, so nothing on it has been through the check that backs every finished page. The table is what the finished lesson is expected to find, and every cell is unverified until a program prints it. (Not machine-checked here.)
One line: An async function can only be awaited from another async function, so async spreads upward through every caller until it reaches something that owns a loop — the coloring problem — and Go and Java's virtual threads are the two designs here that have no colors, because every function may block and the runtime handles it.
The question¶
A synchronous function three levels up needs a value that only an async function produces. It cannot await. It can block on the loop — which deadlocks if it is already on the loop — or become async itself, and so must its callers. The page shows the deadlock, the spread, and the two escape hatches each language offers, then the Go and Java columns where the question does not arise.
What each language is expected to say¶
| Expected answer, to be verified by the program | |
|---|---|
| Rust | a sync fn cannot .await; block_on from inside a runtime panics; the page shows both messages (link for tokio; std column with a channel) |
| Go | no colors: f() blocks in a goroutine and nothing changes upstream |
| C | no colors and no async; every call is a blocking call |
| C++ | a coroutine can only be co_awaited from a coroutine; same spread |
| Java | no colors on a virtual thread; CompletableFuture.join() is the blocking escape |
| Python | asyncio.run inside a running loop raises RuntimeError; nest_asyncio and run_coroutine_threadsafe are the escapes |
What the programs have to show¶
- the sync caller that blocks on the loop from inside it: the panic or
RuntimeErrortext as a key - the spread: the three functions made async, and the one at the top that runs the loop
- Go and Java doing the same call chain with no change
See also¶
- Before this: What happens to the work when an await times out?
- After this: What happens to a task that is started and never awaited?
- What happens at an
await? - What is a goroutine, if not a thread?
- The Rust library's Common async pitfalls ↗
- The Rust library's
async fnand.await↗ - Concepts: Function coloring · Async and await · Virtual thread · Blocking the event loop