How do ```async fn``` futures and ```#[coroutine]``` relate to each other?

Hello, everybody!

Recently I was experimenting with unstable #[coroutine] feature that can be used to create functions (closures) that have two ways of returning -- a temporary return with yield x; statement, after which it can be resumed, and with ordinary return x; statement like any other function. I've tried to use it in some of my pet projects (they actively use various unstable nightly-only features). I wonder, how do coroutines relate to the similar feature in stable Rust -- async/await syntax? I've noted the following:

  • async/await is stable, #[coroutine] and yield statements are not
  • Both async/await and #[coroutine] are available in core, which means both can be used in no-std environments, such as an operating system kernel (I wonder, are there operating systems written in Rust that use async in the kernel?)
  • Coroutines yield when the job is partially finished (some results can be retrieved now), therefore they yield a value, while async functions yield when the job cannot make progress right now (maybe, connection is not established yet, no client has connected, no data has been received, helper process or thread has not finished yet), therefore, there is no "yield value" in Poll::Pending like in CoroutineState::Yielded
  • Async functions require a waker to notify the asynchronous runtime when the job can make progress (the connection is esablished, the client has connected, etc), while coroutines don't need a waker -- they can be resumed immediately
1 Like

async functions and blocks are desugared to coroutines under the hood. The &mut Context (actually, a unsafely wrapped variant of it) is its Resume type while Poll<Self::Output> is the Yield one.

2 Likes

While the RFC for stackless coroutines is quite old and Rust's async/await syntax is now different from what is described in the RFC, I think it is still very informative and describes well how one can implement futures on top of stackless coroutines:

1 Like

“Require” doesn’t correctly describe the difference here — it is always valid to poll a future regardless of waking, and if you are doing this, you can pass Waker::noop() when you do it. Thus, a Future can be “resumed immediately”.

The thing which Future offers is that a Future is expected to use its waker to tell you when it is worth polling; to give you enough information to avoid polling it when polling it will not help it make forward progress, because some outside event hasn’t happened yet.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.