{"article":{"slug":"the-future-trait-has-a-missing-state","title":"The Future Trait Has A Missing State","subtitle":null,"summary":"Yoshua Wuyts argues that Rust's Future trait lacks a way to report that a future has already completed: polling after Ready may panic or misbehave, forcing bookkeeping wrappers like MaybeDone and adding code size. He sketches fixing Poll and CoroutineState with a 'done' state, explores even more states, and urges not repeating the mistake in the Coroutine trait and gen keyword.","content_type":"blog_post","language":"en","canonical_url":"https://blog.yoshuawuyts.com/the-future-trait-has-a-missing-state","author":{"name":"Yoshua Wuyts","url":"https://blog.yoshuawuyts.com/","person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"Yosh Wuyts — Blog","url":"https://blog.yoshuawuyts.com/","listing_slug":null,"listing":null},"topics":[{"name":"Rust","slug":"rust","url":"https://listedarticles.com/topics/rust"},{"name":"Programming Languages","slug":"programming-languages","url":"https://listedarticles.com/topics/programming-languages"},{"name":"Async","slug":"async","url":"https://listedarticles.com/topics/async"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":1739,"reading_minutes":8,"published_at":"2026-10-29T00:00:00.000Z","added_at":"2026-10-29T05:12:26.011Z","updated_at":"2026-10-29T05:12:26.011Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/the-future-trait-has-a-missing-state","markdown_url":"https://listedarticles.com/articles/the-future-trait-has-a-missing-state.md","example":false,"citation":"Yoshua Wuyts, Yosh Wuyts — Blog. \"The Future Trait Has A Missing State.\" 29 Oct 2026. https://blog.yoshuawuyts.com/the-future-trait-has-a-missing-state (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://blog.yoshuawuyts.com/the-future-trait-has-a-missing-state"},"body_markdown":"*Yoshua Wuyts · October 29, 2026*\n\n## Introduction\n\nIn async Rust the `Future::poll` method is called to progress the state of the future, which reports its progress via the `Poll` enum. This enum contains two variants:\n\n- **Poll::Pending**: means that the future is not yet ready to produce a value, and `poll` will need to be invoked again later.\n- **Poll::Ready(T)**: means the future is ready to yield some value `T`.\n\nFutures are called that because they will eventually resolve to a value, *in the future*. But that’s value singular, not values plural. The stdlib documentation does not clearly make this guarantee (though it should), and the ecosystem doesn’t always implement this correctly (though it also should). But the intent is clear: one future should only ever yield one value.\n\nNow what should happen when you try and call `Future::poll` again after it has already yielded a value? The `Future` trait docs have the following to say:\n\n> Once a future has completed (returned `Ready` from `poll`), calling its `poll` method again may panic, block forever, or cause other kinds of problems; the `Future` trait places no requirements on the effects of such a call. However, as the `poll` method is not marked `unsafe`, Rust’s usual rules apply […].\n\nThe docs state here that if you call `Future::poll` again after it’s yielded\n`Poll::Ready`, that’s unsupported and there aren’t many limits on the future’s\nbehavior. The most common behavior here is to `panic!`, which is the behavior\nfor all `async` blocks and functions. Which is a shame, since being able to call\n`Future::poll` again after it’s yielded `Ready` before is actually quite useful\nand something I frequently wish I was able to do.\n\n## MaybeDone\n\nNot everyone writing Rust writes async Rust, and not everyone writing async Rust will write async concurrency primitives. But those who do will be familiar with the `MaybeDone` type. It’s a simple wrapper around some `Future` whose only job it is to either hold a `Future` or if it's completed hold its `Future::Output`. While not simple, assuming you're familiar enough with async Rust it should be easy enough to follow:\n\nrust\n\n```\nuse pin_project::pin_project;\nuse std::future::Future;\nuse std::pin::Pin;\nuse std::task::{Context, Poll};\n\n#[pin_project(project = DoneState)]\nenum MaybeDone<F: Future> {\n    Future(#[pin] F),\n    Done(Option<F::Output>),\n}\n\nimpl<F: Future> MaybeDone<F> {\n    pub fn take(self: Pin<&mut Self>) -> Option<F::Output> {\n        match self.project() {\n            DoneState::Done(output) => output.take(),\n            DoneState::Future(_) => None,\n        }\n    }\n}\n\nimpl<F: Future> Future for MaybeDone<F> {\n    type Output = ();\n\n    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {\n        match self.as_mut().project() {\n            DoneState::Future(future) => match future.poll(cx) {\n                Poll::Pending => Poll::Pending,\n                Poll::Ready(output) => {\n                    self.set(Self::Done(Some(output)));\n                    Poll::Ready(())\n                }\n            },\n            DoneState::Done(_) => Poll::Ready(()),\n        }\n    }\n}\n```\n\nIn essence what this provides is a slot that holds either a future, or its final resolved value. And in doing so it also performs another trick: it replaces the output of the future with the unit type and adds a new method `take` to actually extract the value of the future.\n\nWhen attempting to call `Future::poll` again after it has returned `Poll::Ready(())` once before, it will always continue to return `Poll::Ready(())`. And if you call the `take` method again after having received `Some(Future::Output)` before, it will keep returning `None` forever after.\n\nWhat is a relatively straightforward way of creating a slot that holds either a future or its output value has an interesting side effect of actually being able to communicate the state of the future even after it has yielded a value once. If a caller observes `Poll::Ready(())` + `None`, that means the future is done and will not be yielding a value again.\n\n## Fixing Poll\n\n`MaybeDone` isn’t the only API in the `futures` crate which tracks the done state of a\nfuture. The `FusedFuture` trait provides an `is_terminated` method, and its `Fuse`\nadapter keeps returning `Poll::Pending` once the inner future has returned\n`Poll::Ready`. However this unfortunately abuses `Poll::Pending`, whose docs state:\n\n> When a function returns `Pending`, the function *must* also ensure that the current task is scheduled to be awoken when progress can be made.\n\nThis definition is incompatible with how `Fuse` uses `Pending`, and so bringing\nit into the stdlib would require redefining `Pending` to mean both “pending” and\n“done”. This feels bad, because if you forget to call `is_terminated` first, the\nfuture will hang forever. There are other hacky schemes we could apply instead,\nbut the right way to fix this would be by making the `Done` state a part of\n`Poll` itself:\n\nrust\n\n```\npub enum Poll<T> {\n    Ready(T),\n    Pending,\n    Done,    // ← new\n}\n```\n\nUnfortunately we can’t make this change since `Poll` is not marked\n`#[non_exhaustive]`. Which is a shame since instead of panicking or hanging,\nfutures would actually have an opportunity to share that they have nothing more\nto report. This would change the responsibility of what to do with that\ninformation from the callee to the caller. The caller might still choose to\npanic, but it could also forward the `Done` state, or in concurrency operations\nlike `join` it could now be used to track which futures have completed, without\nneeding `MaybeDone`.\n\nThis would also have the benefit that it removes the implicit dependency of the\nasync effect on the panic effect. The compiler can guarantee that calling\n`.await` never polls after getting `Ready`, and so it doesn’t need to handle the\n`Done` state. With this in place, `async` functions and blocks would no longer\nneed to inject unwinding support on every path, reducing the codegen size which\nespecially benefits embedded targets. It would also work well with the\n`no_panic` crate on crates.io, or an eventual function/block-level no-panic\nannotation.\n\n## Fixing CoroutineState\n\nThe `CoroutineState` enum has the same problems as the `Poll` enum, in that it cannot express a final `Done` state. But unlike `Poll`, the `Coroutine` trait and `CoroutineState` enum aren't stable and so can be fixed without any breakage.\n\nSince `Coroutine` is used internally in compiler desugarings, these changes might still not be easy. But at least we don’t have to repeat the same mistakes we've made for async with iteration as well.\n\nTo better illustrate the problem with `CoroutineState`, here is how it is defined today:\n\nrust\n\n```\npub enum CoroutineState<Y, R> {\n    Yielded(Y),\n    Complete(R),\n}\n```\n\n[The documentation](https://doc.rust-lang.org/std/ops/enum.CoroutineState.html) has the following to say about the `Complete` variant:\n\n> Once a coroutine has returned `Complete` it is considered a programmer error to call `resume` again.\n\n`resume` here refers to `Coroutine::resume`, which drives the progress of the coroutine similar to `Iterator::next` and `Future::poll`. But just like with futures (and unlike iterators[1](#unlike)), coroutines are unable to express a “done” state.\n\n1\n\nTechnically `Iterator::next` returning `None` is no guarantee that `None` will keep being returned. But at the very least returning `None` repeatedly is fine and expected, and so it’s a valid way of encoding a “Done” state, which is what `FusedIterator` does.\n\nThis means that we cannot define a generic `FusedCoroutine` trait like we have with [`FusedIterator`](https://doc.rust-lang.org/std/iter/trait.FusedIterator.html). Since `Complete(R)` takes a generic param we could of course require that `Complete(Option<R>)`, but that would again be dancing around the problem. This is a problem we know we have, we know how to solve, and so we should solve:\n\nrust\n\n```\npub enum CoroutineState<Y, R> {\n    Yielded(Y),\n    Complete(R),\n    Done,         // ← new\n}\n```\n\nThis does leave us with some confusing names. `Complete` here is not the same as `Done`. So I would propose we name the variants after the operations they represent, just like we’ve done with `ControlFlow` (`Break|Continue`):\n\nrust\n\n```\npub enum CoroutineState<Y, R> {\n    Yield(Y),   // ← renamed \n    Return(R),  // ← renamed\n    Done,    \n}\n```\n\n## Even more states\n\nSo far we’ve covered the three future states: `Ready`, `Pending`, and `Done`.\nThese are the three states we communicate *externally*. But internally there\nis one more state: `Start` or `Init`. This is the state a future is in *before*\nit is first polled. But since we never yield that state, it’s internal-only.\n\nAnother state we haven’t covered here is the state *before* the future or\ncoroutine is initialized. In async Rust `.await` calls the\n`IntoFuture::into_future` method before polling the future, just like when\niterating the `for..in` notation calls `IntoIterator::into_iter` before calling\n`Iterator::next`.\n\nI’m not sure what to call this pre-state, but its purpose is to capture inputs\nwithout needing to instantiate locals. Unfortunately `async {}` currently\nreturns `impl Future` rather than `impl IntoFuture`. In [the gen auto-trait\nproblem](https://blog.yoshuawuyts.com/gen-auto-trait-problem) I explained how\nthat would help us create more precise bounds and solve a number of `Send`/`Sync`\ninteractions. While changing the desugaring of `async {}` is tricky, `gen {}` is\nstill unstable and is much easier to change.\n\nFinally, in [Never Snooze a Future](https://jacko.io/snooze.html) Jack O’Connor\ndescribes what ought to be an invalid future state: “Snoozed”. A future is\nsnoozed when it is ready to make progress, but it’s not polled. I think of this as\na way of translating the [“bbbs”\nscenario](https://rust-lang.github.io/wg-async/vision/submitted_stories/status_quo/barbara_battles_buffered_streams.html)[1](#bbbs)\ninto a liveness property.\n\n1\n\nBarbara Battles Buffered Streams\n\nFutures [naturally form\ntrees](https://blog.yoshuawuyts.com/tree-structured-concurrency/index). The\n“snoozed” state applies when some child future’s waker is called, and its parent is\npolled but the child isn’t. This leads to a rule that we can apply recursively\nto every future: *“When a future is polled, it must also poll all child futures\nthat have had their waker called.”* If every future follows this rule, no future\nwill ever be snoozed.\n\n## Conclusion\n\nThe fact that futures are unable to signal being done is a problem, and one\nwhich has no good workarounds. Types like `MaybeDone` can work around this\nlimitation on use, but do so by introducing redundant bookkeeping.\n\nTracking the completion status of futures is necessary for the implementation of\nnearly all async concurrency operations. Instead `async {}` blocks desugar to\nfutures which panic when polled again after completion. This creates problems in\nconstrained environments like embedded and web where code size matters.\n\nWe cannot fix the `Future` trait without deprecating the existing `Future::poll`\nmethod. This post is the fourth entry in my series analyzing problems with the\n`Future` trait, and even so I don’t think that’s enough yet to warrant replacing\nwhat we have. What we can do however is make sure we don’t repeat the mistakes\nwe made in `Future`, for example by fixing the `Coroutine` trait and `gen`\nkeyword.\n\n---\n\n*Originally published at [Yosh Wuyts' blog](https://blog.yoshuawuyts.com/the-future-trait-has-a-missing-state).*\n","body_html":"<p><em>Yoshua Wuyts · October 29, 2026</em></p>\n<h2 id=\"introduction\">Introduction</h2>\n<p>In async Rust the <code>Future::poll</code> method is called to progress the state of the future, which reports its progress via the <code>Poll</code> enum. This enum contains two variants:</p>\n<ul><li><strong>Poll::Pending</strong>: means that the future is not yet ready to produce a value, and <code>poll</code> will need to be invoked again later.</li><li><strong>Poll::Ready(T)</strong>: means the future is ready to yield some value <code>T</code>.</li></ul>\n<p>Futures are called that because they will eventually resolve to a value, <em>in the future</em>. But that’s value singular, not values plural. The stdlib documentation does not clearly make this guarantee (though it should), and the ecosystem doesn’t always implement this correctly (though it also should). But the intent is clear: one future should only ever yield one value.</p>\n<p>Now what should happen when you try and call <code>Future::poll</code> again after it has already yielded a value? The <code>Future</code> trait docs have the following to say:</p>\n<blockquote><p>Once a future has completed (returned <code>Ready</code> from <code>poll</code>), calling its <code>poll</code> method again may panic, block forever, or cause other kinds of problems; the <code>Future</code> trait places no requirements on the effects of such a call. However, as the <code>poll</code> method is not marked <code>unsafe</code>, Rust’s usual rules apply […].</p></blockquote>\n<p>The docs state here that if you call <code>Future::poll</code> again after it’s yielded\n<code>Poll::Ready</code>, that’s unsupported and there aren’t many limits on the future’s\nbehavior. The most common behavior here is to <code>panic!</code>, which is the behavior\nfor all <code>async</code> blocks and functions. Which is a shame, since being able to call\n<code>Future::poll</code> again after it’s yielded <code>Ready</code> before is actually quite useful\nand something I frequently wish I was able to do.</p>\n<h2 id=\"maybedone\">MaybeDone</h2>\n<p>Not everyone writing Rust writes async Rust, and not everyone writing async Rust will write async concurrency primitives. But those who do will be familiar with the <code>MaybeDone</code> type. It’s a simple wrapper around some <code>Future</code> whose only job it is to either hold a <code>Future</code> or if it&#39;s completed hold its <code>Future::Output</code>. While not simple, assuming you&#39;re familiar enough with async Rust it should be easy enough to follow:</p>\n<p>rust</p>\n<pre><code>use pin_project::pin_project;\nuse std::future::Future;\nuse std::pin::Pin;\nuse std::task::{Context, Poll};\n\n#[pin_project(project = DoneState)]\nenum MaybeDone&lt;F: Future&gt; {\n    Future(#[pin] F),\n    Done(Option&lt;F::Output&gt;),\n}\n\nimpl&lt;F: Future&gt; MaybeDone&lt;F&gt; {\n    pub fn take(self: Pin&lt;&amp;mut Self&gt;) -&gt; Option&lt;F::Output&gt; {\n        match self.project() {\n            DoneState::Done(output) =&gt; output.take(),\n            DoneState::Future(_) =&gt; None,\n        }\n    }\n}\n\nimpl&lt;F: Future&gt; Future for MaybeDone&lt;F&gt; {\n    type Output = ();\n\n    fn poll(mut self: Pin&lt;&amp;mut Self&gt;, cx: &amp;mut Context&lt;&#39;_&gt;) -&gt; Poll&lt;()&gt; {\n        match self.as_mut().project() {\n            DoneState::Future(future) =&gt; match future.poll(cx) {\n                Poll::Pending =&gt; Poll::Pending,\n                Poll::Ready(output) =&gt; {\n                    self.set(Self::Done(Some(output)));\n                    Poll::Ready(())\n                }\n            },\n            DoneState::Done(_) =&gt; Poll::Ready(()),\n        }\n    }\n}</code></pre>\n<p>In essence what this provides is a slot that holds either a future, or its final resolved value. And in doing so it also performs another trick: it replaces the output of the future with the unit type and adds a new method <code>take</code> to actually extract the value of the future.</p>\n<p>When attempting to call <code>Future::poll</code> again after it has returned <code>Poll::Ready(())</code> once before, it will always continue to return <code>Poll::Ready(())</code>. And if you call the <code>take</code> method again after having received <code>Some(Future::Output)</code> before, it will keep returning <code>None</code> forever after.</p>\n<p>What is a relatively straightforward way of creating a slot that holds either a future or its output value has an interesting side effect of actually being able to communicate the state of the future even after it has yielded a value once. If a caller observes <code>Poll::Ready(())</code> + <code>None</code>, that means the future is done and will not be yielding a value again.</p>\n<h2 id=\"fixing-poll\">Fixing Poll</h2>\n<p><code>MaybeDone</code> isn’t the only API in the <code>futures</code> crate which tracks the done state of a\nfuture. The <code>FusedFuture</code> trait provides an <code>is_terminated</code> method, and its <code>Fuse</code>\nadapter keeps returning <code>Poll::Pending</code> once the inner future has returned\n<code>Poll::Ready</code>. However this unfortunately abuses <code>Poll::Pending</code>, whose docs state:</p>\n<blockquote><p>When a function returns <code>Pending</code>, the function <em>must</em> also ensure that the current task is scheduled to be awoken when progress can be made.</p></blockquote>\n<p>This definition is incompatible with how <code>Fuse</code> uses <code>Pending</code>, and so bringing\nit into the stdlib would require redefining <code>Pending</code> to mean both “pending” and\n“done”. This feels bad, because if you forget to call <code>is_terminated</code> first, the\nfuture will hang forever. There are other hacky schemes we could apply instead,\nbut the right way to fix this would be by making the <code>Done</code> state a part of\n<code>Poll</code> itself:</p>\n<p>rust</p>\n<pre><code>pub enum Poll&lt;T&gt; {\n    Ready(T),\n    Pending,\n    Done,    // ← new\n}</code></pre>\n<p>Unfortunately we can’t make this change since <code>Poll</code> is not marked\n<code>#[non_exhaustive]</code>. Which is a shame since instead of panicking or hanging,\nfutures would actually have an opportunity to share that they have nothing more\nto report. This would change the responsibility of what to do with that\ninformation from the callee to the caller. The caller might still choose to\npanic, but it could also forward the <code>Done</code> state, or in concurrency operations\nlike <code>join</code> it could now be used to track which futures have completed, without\nneeding <code>MaybeDone</code>.</p>\n<p>This would also have the benefit that it removes the implicit dependency of the\nasync effect on the panic effect. The compiler can guarantee that calling\n<code>.await</code> never polls after getting <code>Ready</code>, and so it doesn’t need to handle the\n<code>Done</code> state. With this in place, <code>async</code> functions and blocks would no longer\nneed to inject unwinding support on every path, reducing the codegen size which\nespecially benefits embedded targets. It would also work well with the\n<code>no_panic</code> crate on crates.io, or an eventual function/block-level no-panic\nannotation.</p>\n<h2 id=\"fixing-coroutinestate\">Fixing CoroutineState</h2>\n<p>The <code>CoroutineState</code> enum has the same problems as the <code>Poll</code> enum, in that it cannot express a final <code>Done</code> state. But unlike <code>Poll</code>, the <code>Coroutine</code> trait and <code>CoroutineState</code> enum aren&#39;t stable and so can be fixed without any breakage.</p>\n<p>Since <code>Coroutine</code> is used internally in compiler desugarings, these changes might still not be easy. But at least we don’t have to repeat the same mistakes we&#39;ve made for async with iteration as well.</p>\n<p>To better illustrate the problem with <code>CoroutineState</code>, here is how it is defined today:</p>\n<p>rust</p>\n<pre><code>pub enum CoroutineState&lt;Y, R&gt; {\n    Yielded(Y),\n    Complete(R),\n}</code></pre>\n<p><a href=\"https://doc.rust-lang.org/std/ops/enum.CoroutineState.html\" rel=\"nofollow ugc noopener\">The documentation</a> has the following to say about the <code>Complete</code> variant:</p>\n<blockquote><p>Once a coroutine has returned <code>Complete</code> it is considered a programmer error to call <code>resume</code> again.</p></blockquote>\n<p><code>resume</code> here refers to <code>Coroutine::resume</code>, which drives the progress of the coroutine similar to <code>Iterator::next</code> and <code>Future::poll</code>. But just like with futures (and unlike iterators<a href=\"#unlike\">1</a>), coroutines are unable to express a “done” state.</p>\n<p>1</p>\n<p>Technically <code>Iterator::next</code> returning <code>None</code> is no guarantee that <code>None</code> will keep being returned. But at the very least returning <code>None</code> repeatedly is fine and expected, and so it’s a valid way of encoding a “Done” state, which is what <code>FusedIterator</code> does.</p>\n<p>This means that we cannot define a generic <code>FusedCoroutine</code> trait like we have with <a href=\"https://doc.rust-lang.org/std/iter/trait.FusedIterator.html\" rel=\"nofollow ugc noopener\"><code>FusedIterator</code></a>. Since <code>Complete(R)</code> takes a generic param we could of course require that <code>Complete(Option&lt;R&gt;)</code>, but that would again be dancing around the problem. This is a problem we know we have, we know how to solve, and so we should solve:</p>\n<p>rust</p>\n<pre><code>pub enum CoroutineState&lt;Y, R&gt; {\n    Yielded(Y),\n    Complete(R),\n    Done,         // ← new\n}</code></pre>\n<p>This does leave us with some confusing names. <code>Complete</code> here is not the same as <code>Done</code>. So I would propose we name the variants after the operations they represent, just like we’ve done with <code>ControlFlow</code> (<code>Break|Continue</code>):</p>\n<p>rust</p>\n<pre><code>pub enum CoroutineState&lt;Y, R&gt; {\n    Yield(Y),   // ← renamed \n    Return(R),  // ← renamed\n    Done,    \n}</code></pre>\n<h2 id=\"even-more-states\">Even more states</h2>\n<p>So far we’ve covered the three future states: <code>Ready</code>, <code>Pending</code>, and <code>Done</code>.\nThese are the three states we communicate <em>externally</em>. But internally there\nis one more state: <code>Start</code> or <code>Init</code>. This is the state a future is in <em>before</em>\nit is first polled. But since we never yield that state, it’s internal-only.</p>\n<p>Another state we haven’t covered here is the state <em>before</em> the future or\ncoroutine is initialized. In async Rust <code>.await</code> calls the\n<code>IntoFuture::into_future</code> method before polling the future, just like when\niterating the <code>for..in</code> notation calls <code>IntoIterator::into_iter</code> before calling\n<code>Iterator::next</code>.</p>\n<p>I’m not sure what to call this pre-state, but its purpose is to capture inputs\nwithout needing to instantiate locals. Unfortunately <code>async {}</code> currently\nreturns <code>impl Future</code> rather than <code>impl IntoFuture</code>. In <a href=\"https://blog.yoshuawuyts.com/gen-auto-trait-problem\" rel=\"nofollow ugc noopener\">the gen auto-trait\nproblem</a> I explained how\nthat would help us create more precise bounds and solve a number of <code>Send</code>/<code>Sync</code>\ninteractions. While changing the desugaring of <code>async {}</code> is tricky, <code>gen {}</code> is\nstill unstable and is much easier to change.</p>\n<p>Finally, in <a href=\"https://jacko.io/snooze.html\" rel=\"nofollow ugc noopener\">Never Snooze a Future</a> Jack O’Connor\ndescribes what ought to be an invalid future state: “Snoozed”. A future is\nsnoozed when it is ready to make progress, but it’s not polled. I think of this as\na way of translating the <a href=\"https://rust-lang.github.io/wg-async/vision/submitted_stories/status_quo/barbara_battles_buffered_streams.html\" rel=\"nofollow ugc noopener\">“bbbs”\nscenario</a><a href=\"#bbbs\">1</a>\ninto a liveness property.</p>\n<p>1</p>\n<p>Barbara Battles Buffered Streams</p>\n<p>Futures <a href=\"https://blog.yoshuawuyts.com/tree-structured-concurrency/index\" rel=\"nofollow ugc noopener\">naturally form\ntrees</a>. The\n“snoozed” state applies when some child future’s waker is called, and its parent is\npolled but the child isn’t. This leads to a rule that we can apply recursively\nto every future: *“When a future is polled, it must also poll all child futures\nthat have had their waker called.”* If every future follows this rule, no future\nwill ever be snoozed.</p>\n<h2 id=\"conclusion\">Conclusion</h2>\n<p>The fact that futures are unable to signal being done is a problem, and one\nwhich has no good workarounds. Types like <code>MaybeDone</code> can work around this\nlimitation on use, but do so by introducing redundant bookkeeping.</p>\n<p>Tracking the completion status of futures is necessary for the implementation of\nnearly all async concurrency operations. Instead <code>async {}</code> blocks desugar to\nfutures which panic when polled again after completion. This creates problems in\nconstrained environments like embedded and web where code size matters.</p>\n<p>We cannot fix the <code>Future</code> trait without deprecating the existing <code>Future::poll</code>\nmethod. This post is the fourth entry in my series analyzing problems with the\n<code>Future</code> trait, and even so I don’t think that’s enough yet to warrant replacing\nwhat we have. What we can do however is make sure we don’t repeat the mistakes\nwe made in <code>Future</code>, for example by fixing the <code>Coroutine</code> trait and <code>gen</code>\nkeyword.</p>\n<hr />\n<p><em>Originally published at <a href=\"https://blog.yoshuawuyts.com/the-future-trait-has-a-missing-state\" rel=\"nofollow ugc noopener\">Yosh Wuyts&#39; blog</a>.</em></p>","headings":[{"level":2,"text":"Introduction","id":"introduction"},{"level":2,"text":"MaybeDone","id":"maybedone"},{"level":2,"text":"Fixing Poll","id":"fixing-poll"},{"level":2,"text":"Fixing CoroutineState","id":"fixing-coroutinestate"},{"level":2,"text":"Even more states","id":"even-more-states"},{"level":2,"text":"Conclusion","id":"conclusion"}]}}