├── rustfmt.toml ├── rust-toolchain.toml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── .github ├── dependabot.yml └── workflows │ └── rust.yml ├── SUPPORT.md ├── Cargo.toml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── SECURITY.md ├── README.md └── src ├── tests.rs └── lib.rs /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2024" 2 | reorder_imports = true 3 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | components = ["rustfmt", "clippy"] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## How to file issues and get help 4 | 5 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 6 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 7 | feature request as a new Issue. 8 | 9 | For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE 10 | FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER 11 | CHANNEL. WHERE WILL YOU HELP PEOPLE?**. 12 | 13 | ## Microsoft Support Policy 14 | 15 | Support for this PROJECT is limited to the resources listed above. 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | [package] 5 | name = "stackfuture" 6 | description = "StackFuture is a wrapper around futures that stores the wrapped future in space provided by the caller." 7 | version = "0.3.1" 8 | edition = "2024" 9 | license = "MIT" 10 | authors = ["Microsoft"] 11 | repository = "https://github.com/microsoft/stackfuture" 12 | keywords = ["async", "no_std", "futures"] 13 | categories = ["asynchronous", "no-std", "rust-patterns"] 14 | 15 | [dependencies] 16 | const_panic = "0.2" 17 | 18 | [dev-dependencies] 19 | futures = { version = "0.3", features = ["executor"] } 20 | 21 | [features] 22 | default = ["alloc"] 23 | alloc = [] 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [0.3.1](https://github.com/microsoft/stackfuture/compare/v0.3.0...v0.3.1) - 2025-11-18 11 | 12 | ### Other 13 | 14 | - Change 'F' to 'Future' in panic messages 15 | - Print detailed panic messages 16 | - Potential fix for code scanning alert no. 2: Workflow does not contain permissions 17 | - Fix another dead_code issue 18 | - cargo fmt 19 | - General updates, cleanups, and fixes 20 | - Add auto-updates from dependabot 21 | - Add missing copyright header and fix SUPPORT.md 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to 4 | agree to a Contributor License Agreement (CLA) declaring that you have the right to, 5 | and actually do, grant us the rights to use your contribution. For details, visit 6 | https://cla.microsoft.com. 7 | 8 | When you submit a pull request, a CLA-bot will automatically determine whether you need 9 | to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the 10 | instructions provided by the bot. You will only need to do this once across all repositories using our CLA. 11 | 12 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 13 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 14 | or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 15 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | StackFuture 2 | Copyright (c) Microsoft Corporation. 3 | 4 | MIT License 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | permissions: 3 | contents: read 4 | 5 | on: 6 | push: 7 | branches: [ "main" ] 8 | pull_request: 9 | branches: [ "main" ] 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | 14 | jobs: 15 | build: 16 | strategy: 17 | matrix: 18 | features: ["", "--no-default-features"] 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Format 23 | run: cargo fmt --all -- --check 24 | - name: Check 25 | run: cargo check --all-features --all-targets 26 | - name: Clippy 27 | run: cargo clippy --all-features --all-targets -- -D warnings 28 | - name: Build 29 | run: cargo build --verbose ${{ matrix.features }} 30 | - name: Run tests 31 | run: cargo test --verbose ${{ matrix.features }} 32 | 33 | miri: 34 | name: "Miri" 35 | strategy: 36 | matrix: 37 | features: ["", "--no-default-features"] 38 | runs-on: ubuntu-latest 39 | steps: 40 | - uses: actions/checkout@v3 41 | - name: Install Miri 42 | run: | 43 | rustup toolchain install nightly --component miri 44 | rustup override set nightly 45 | cargo miri setup 46 | - name: Test with Miri 47 | run: cargo miri test ${{ matrix.features }} 48 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StackFuture 2 | 3 | [![crates.io](https://img.shields.io/crates/v/stackfuture.svg)](https://crates.io/crates/stackfuture) 4 | [![docs.rs](https://img.shields.io/docsrs/stackfuture)](https://docs.rs/stackfuture/) 5 | 6 | This crate defines a `StackFuture` wrapper around futures that stores the wrapped future in space provided by the caller. 7 | This can be used to emulate dynamic async traits without requiring heap allocation. 8 | Below is an example of how use `StackFuture`: 9 | 10 | ```rust 11 | use stackfuture::*; 12 | 13 | trait PseudoAsyncTrait { 14 | fn do_something(&self) -> StackFuture<'static, (), { 512 }>; 15 | } 16 | 17 | impl PseudoAsyncTrait for i32 { 18 | fn do_something(&self) -> StackFuture<'static, (), { 512 }> { 19 | StackFuture::from(async { 20 | // function body goes here 21 | }) 22 | } 23 | } 24 | 25 | async fn use_dyn_async_trait(x: &dyn PseudoAsyncTrait) { 26 | x.do_something().await; 27 | } 28 | 29 | async fn call_with_dyn_async_trait() { 30 | use_dyn_async_trait(&42).await; 31 | } 32 | ``` 33 | 34 | This is most useful for cases where async functions in `dyn Trait` objects are needed but storing them in a `Box` is not feasible. 35 | Such cases include embedded programming where allocation is not available, or in tight inner loops where the performance overhead for allocation is unacceptable. 36 | Note that doing this involves tradeoffs. 37 | In the case of `StackFuture`, you must set a compile-time limit on the maximum size of future that will be supported. 38 | If you need to support async functions in `dyn Trait` objects but these constraints do not apply to you, you may be better served by the [`async-trait`](https://crates.io/crates/async-trait) crate. 39 | 40 | ## Contributing 41 | 42 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 43 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 44 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 45 | 46 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 47 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 48 | provided by the bot. You will only need to do this once across all repos using our CLA. 49 | 50 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 51 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 52 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 53 | 54 | ## Trademarks 55 | 56 | This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft 57 | trademarks or logos is subject to and must follow 58 | [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). 59 | Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. 60 | Any use of third-party trademarks or logos are subject to those third-party's policies. 61 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | use crate::StackFuture; 5 | use core::task::Poll; 6 | use futures::Future; 7 | use futures::SinkExt; 8 | use futures::Stream; 9 | use futures::StreamExt; 10 | use futures::channel::mpsc; 11 | use futures::executor::block_on; 12 | use futures::pin_mut; 13 | use std::sync::Arc; 14 | use std::task::Context; 15 | use std::task::Wake; 16 | use std::thread; 17 | 18 | #[test] 19 | fn create_and_run() { 20 | // A smoke test. Make sure we can create a future, run it, and get a value out. 21 | let f = StackFuture::<'_, _, 8>::from(async { 5 }); 22 | assert_eq!(block_on(f), 5); 23 | } 24 | 25 | /// A type that is uninhabited and therefore can never be constructed. 26 | enum Never {} 27 | 28 | /// A future whose poll function always returns `Pending` 29 | /// 30 | /// Used to force a suspend point so we can test behaviors with suspended futures. 31 | struct SuspendPoint; 32 | impl Future for SuspendPoint { 33 | type Output = Never; 34 | 35 | fn poll( 36 | self: std::pin::Pin<&mut Self>, 37 | _cx: &mut std::task::Context<'_>, 38 | ) -> std::task::Poll { 39 | Poll::Pending 40 | } 41 | } 42 | 43 | /// A waker that doesn't do anything. 44 | /// 45 | /// Needed so we can create a context and manually call poll. 46 | struct Waker; 47 | impl Wake for Waker { 48 | fn wake(self: std::sync::Arc) { 49 | unimplemented!() 50 | } 51 | } 52 | 53 | #[test] 54 | fn destructor_runs() { 55 | // A test to ensure `StackFuture` correctly calls the destructor of the underlying future. 56 | // 57 | // We do this by creating a manually implemented future whose destructor sets a boolean 58 | // indicating it ran. We create such a value (the `let _ = DropMe(&mut destructed))` line 59 | // below), then use `SuspendPoint.await` to suspend the future. 60 | // 61 | // The driver code creates a context and then calls poll once on the future so that the 62 | // DropMe object will be created. We then let the future go out of scope so the destructor 63 | // will run. 64 | let mut destructed = false; 65 | let _poll_result = { 66 | let f = async { 67 | struct DropMe<'a>(&'a mut bool); 68 | impl Drop for DropMe<'_> { 69 | fn drop(&mut self) { 70 | *self.0 = true; 71 | } 72 | } 73 | let _ = DropMe(&mut destructed); 74 | SuspendPoint.await 75 | }; 76 | let f = StackFuture::<'_, _, 32>::from(f); 77 | 78 | let waker = Arc::new(Waker).into(); 79 | let mut cx = Context::from_waker(&waker); 80 | pin_mut!(f); 81 | f.poll(&mut cx) 82 | }; 83 | assert!(destructed); 84 | } 85 | 86 | #[test] 87 | fn test_size_failure() { 88 | async fn fill_buf(buf: &mut [u8]) { 89 | buf[0] = 42; 90 | } 91 | 92 | let f = async { 93 | let mut buf = [0u8; 256]; 94 | fill_buf(&mut buf).await; 95 | buf[0] 96 | }; 97 | 98 | match StackFuture::<_, 4>::try_from(f) { 99 | Ok(_) => panic!("conversion to StackFuture should not have succeeded"), 100 | Err(e) => assert!(e.insufficient_space()), 101 | } 102 | } 103 | 104 | #[test] 105 | fn test_alignment() { 106 | // A test to make sure we store the wrapped future with the correct alignment 107 | 108 | #[repr(align(8))] 109 | #[allow(dead_code)] 110 | struct BigAlignment(u32); 111 | 112 | impl Future for BigAlignment { 113 | type Output = Never; 114 | 115 | fn poll(self: std::pin::Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { 116 | Poll::Pending 117 | } 118 | } 119 | let mut f = StackFuture::<'_, _, 1016>::from(BigAlignment(42)); 120 | assert!(is_aligned(f.as_mut_ptr::(), 8)); 121 | } 122 | 123 | #[test] 124 | fn test_alignment_failure() { 125 | // A test to make sure we store the wrapped future with the correct alignment 126 | 127 | #[repr(align(256))] 128 | #[allow(dead_code)] 129 | struct BigAlignment(u32); 130 | 131 | impl Future for BigAlignment { 132 | type Output = Never; 133 | 134 | fn poll(self: std::pin::Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { 135 | Poll::Pending 136 | } 137 | } 138 | match StackFuture::<'_, _, 1016>::try_from(BigAlignment(42)) { 139 | Ok(_) => panic!("conversion to StackFuture should not have succeeded"), 140 | Err(e) => assert!(e.alignment_too_small()), 141 | } 142 | } 143 | 144 | #[cfg(feature = "alloc")] 145 | #[test] 146 | fn test_boxed_alignment() { 147 | // A test to make sure we store the wrapped future with the correct alignment 148 | 149 | #[repr(align(256))] 150 | #[allow(dead_code)] 151 | struct BigAlignment(u32); 152 | 153 | impl Future for BigAlignment { 154 | type Output = Never; 155 | 156 | fn poll(self: std::pin::Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { 157 | Poll::Pending 158 | } 159 | } 160 | StackFuture::<'_, _, 1016>::from_or_box(BigAlignment(42)); 161 | } 162 | 163 | /// Returns whether `ptr` is aligned with the given alignment 164 | /// 165 | /// `alignment` must be a power of two. 166 | fn is_aligned(ptr: *mut T, alignment: usize) -> bool { 167 | (ptr as usize) & (alignment - 1) == 0 168 | } 169 | 170 | #[test] 171 | fn stress_drop_sender() { 172 | // Regression test for #9 173 | 174 | const ITER: usize = if cfg!(miri) { 10 } else { 10000 }; 175 | 176 | fn list() -> impl Stream { 177 | let (tx, rx) = mpsc::channel(1); 178 | thread::spawn(move || { 179 | block_on(send_one_two_three(tx)); 180 | }); 181 | rx 182 | } 183 | 184 | for _ in 0..ITER { 185 | let v: Vec<_> = block_on(list().collect()); 186 | assert_eq!(v, vec![1, 2, 3]); 187 | } 188 | } 189 | 190 | fn send_one_two_three(mut tx: mpsc::Sender) -> StackFuture<'static, (), 512> { 191 | StackFuture::from(async move { 192 | for i in 1..=3 { 193 | tx.send(i).await.unwrap(); 194 | } 195 | }) 196 | } 197 | 198 | #[test] 199 | fn try_from() { 200 | let big_future = StackFuture::<_, 1000>::from(async {}); 201 | 202 | match StackFuture::<_, 10>::try_from(big_future) { 203 | Ok(_) => panic!("try_from should not have succeeded"), 204 | Err(big_future) => { 205 | assert!(StackFuture::<_, 1500>::try_from(big_future.into_inner()).is_ok()) 206 | } 207 | }; 208 | } 209 | 210 | #[cfg(feature = "alloc")] 211 | #[test] 212 | fn from_or_box() { 213 | let big_future = StackFuture::<_, 1000>::from(async {}); 214 | StackFuture::<_, 32>::from_or_box(big_future); 215 | } 216 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | //! This crate defines a `StackFuture` wrapper around futures that stores the wrapped 5 | //! future in space provided by the caller. This can be used to emulate dyn async traits 6 | //! without requiring heap allocation. 7 | //! 8 | //! For more details, see the documentation on the [`StackFuture`] struct. 9 | 10 | // std is needed to run tests, but otherwise we don't need it. 11 | #![cfg_attr(not(test), no_std)] 12 | #![warn(missing_docs)] 13 | 14 | use const_panic::concat_panic; 15 | use core::fmt::Debug; 16 | use core::fmt::Display; 17 | use core::future::Future; 18 | use core::marker::PhantomData; 19 | use core::mem; 20 | use core::mem::MaybeUninit; 21 | use core::pin::Pin; 22 | use core::ptr; 23 | use core::task::Context; 24 | use core::task::Poll; 25 | 26 | #[cfg(feature = "alloc")] 27 | extern crate alloc; 28 | 29 | #[cfg(feature = "alloc")] 30 | use alloc::boxed::Box; 31 | 32 | /// A wrapper that stores a future in space allocated by the container 33 | /// 34 | /// Often this space comes from the calling function's stack, but it could just 35 | /// as well come from some other allocation. 36 | /// 37 | /// A `StackFuture` can be used to emulate async functions in dyn Trait objects. 38 | /// For example: 39 | /// 40 | /// ``` 41 | /// # use stackfuture::*; 42 | /// trait PseudoAsyncTrait { 43 | /// fn do_something(&self) -> StackFuture<'_, (), { 512 }>; 44 | /// } 45 | /// 46 | /// impl PseudoAsyncTrait for i32 { 47 | /// fn do_something(&self) -> StackFuture<'_, (), { 512 }> { 48 | /// StackFuture::from(async { 49 | /// // function body goes here 50 | /// }) 51 | /// } 52 | /// } 53 | /// 54 | /// async fn use_dyn_async_trait(x: &dyn PseudoAsyncTrait) { 55 | /// x.do_something().await; 56 | /// } 57 | /// 58 | /// async fn call_with_dyn_async_trait() { 59 | /// use_dyn_async_trait(&42).await; 60 | /// } 61 | /// ``` 62 | /// 63 | /// This example defines `PseudoAsyncTrait` with a single method `do_something`. 64 | /// The `do_something` method can be called as if it were declared as 65 | /// `async fn do_something(&self)`. To implement `do_something`, the easiest thing 66 | /// to do is to wrap the body of the function in `StackFuture::from(async { ... })`, 67 | /// which creates an anonymous future for the body and stores it in a `StackFuture`. 68 | /// 69 | /// Because `StackFuture` does not know the size of the future it wraps, the maximum 70 | /// size of the future must be specified in the `STACK_SIZE` parameter. In the example 71 | /// here, we've used a stack size of 512, which is probably much larger than necessary 72 | /// but would accommodate many futures besides the simple one we've shown here. 73 | /// 74 | /// `StackFuture` ensures when wrapping a future that enough space is available, and 75 | /// it also respects any alignment requirements for the wrapped future. Note that the 76 | /// wrapped future's alignment must be less than or equal to that of the overall 77 | /// `StackFuture` struct. 78 | #[repr(C)] // Ensures the data first does not have any padding before it in the struct 79 | pub struct StackFuture<'a, T, const STACK_SIZE: usize> { 80 | /// An array of bytes that is used to store the wrapped future. 81 | data: [MaybeUninit; STACK_SIZE], 82 | /// Since the type of `StackFuture` does not know the underlying future that it is wrapping, 83 | /// we keep a manual vtable that serves pointers to Poll::poll and Drop::drop. These are 84 | /// generated and filled in by `StackFuture::from`. 85 | /// 86 | /// This field stores a pointer to the poll function wrapper. 87 | poll_fn: fn(this: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll, 88 | /// Stores a pointer to the drop function wrapper 89 | /// 90 | /// See the documentation on `poll_fn` for more details. 91 | drop_fn: fn(this: &mut Self), 92 | /// StackFuture can be used similarly to a `dyn Future`. We keep a PhantomData 93 | /// here so the type system knows this. 94 | _phantom: PhantomData + Send + 'a>, 95 | } 96 | 97 | impl<'a, T, const STACK_SIZE: usize> StackFuture<'a, T, { STACK_SIZE }> { 98 | /// Creates a `StackFuture` from an existing future 99 | /// 100 | /// See the documentation on [`StackFuture`] for examples of how to use this. 101 | /// 102 | /// The size and alignment requirements are statically checked, so it is a compiler error 103 | /// to use this with a future that does not fit within the [`StackFuture`]'s size and 104 | /// alignment requirements. 105 | /// 106 | /// The following example illustrates a compile error for a future that is too large. 107 | /// ```compile_fail 108 | /// # use stackfuture::StackFuture; 109 | /// // Fails because the future contains a large array and is therefore too big to fit in 110 | /// // a 16-byte `StackFuture`. 111 | /// let f = StackFuture::<_, { 16 }>::from(async { 112 | /// let x = [0u8; 4096]; 113 | /// async {}.await; 114 | /// println!("{}", x.len()); 115 | /// }); 116 | /// # #[cfg(miri)] break rust; // FIXME: miri doesn't detect this breakage for some reason... 117 | /// ``` 118 | /// 119 | /// The example below illustrates a compiler error for a future whose alignment is too large. 120 | /// ```compile_fail 121 | /// # use stackfuture::StackFuture; 122 | /// 123 | /// #[derive(Debug)] 124 | /// #[repr(align(256))] 125 | /// struct BigAlignment(usize); 126 | /// 127 | /// // Fails because the future contains a large array and is therefore too big to fit in 128 | /// // a 16-byte `StackFuture`. 129 | /// let f = StackFuture::<_, { 16 }>::from(async { 130 | /// let x = BigAlignment(42); 131 | /// async {}.await; 132 | /// println!("{x:?}"); 133 | /// }); 134 | /// # #[cfg(miri)] break rust; // FIXME: miri doesn't detect this breakage for some reason... 135 | /// ``` 136 | pub fn from(future: F) -> Self 137 | where 138 | F: Future + Send + 'a, // the bounds here should match those in the _phantom field 139 | { 140 | // Ideally we would provide this as: 141 | // 142 | // impl<'a, F, const STACK_SIZE: usize> From for StackFuture<'a, F::Output, { STACK_SIZE }> 143 | // where 144 | // F: Future + Send + 'a 145 | // 146 | // However, libcore provides a blanket `impl From for T`, and since `StackFuture: Future`, 147 | // both impls end up being applicable to do `From for StackFuture`. 148 | 149 | // Statically assert that `F` meets all the size and alignment requirements 150 | #[allow(clippy::let_unit_value)] 151 | let _ = AssertFits::::ASSERT; 152 | 153 | Self::try_from(future).unwrap() 154 | } 155 | 156 | /// Attempts to create a `StackFuture` from an existing future 157 | /// 158 | /// If the `StackFuture` is not large enough to hold `future`, this function returns an 159 | /// `Err` with the argument `future` returned to you. 160 | /// 161 | /// Panics 162 | /// 163 | /// If we cannot satisfy the alignment requirements for `F`, this function will panic. 164 | pub fn try_from(future: F) -> Result> 165 | where 166 | F: Future + Send + 'a, // the bounds here should match those in the _phantom field 167 | { 168 | if Self::has_space_for_val(&future) && Self::has_alignment_for_val(&future) { 169 | let mut result = StackFuture { 170 | data: [MaybeUninit::uninit(); STACK_SIZE], 171 | poll_fn: Self::poll_inner::, 172 | drop_fn: Self::drop_inner::, 173 | _phantom: PhantomData, 174 | }; 175 | 176 | // Ensure result.data is at the beginning of the struct so we don't need to do 177 | // alignment adjustments. 178 | assert_eq!(result.data.as_ptr() as usize, &result as *const _ as usize); 179 | 180 | // SAFETY: result.as_mut_ptr returns a pointer into result.data, which is an 181 | // uninitialized array of bytes. result.as_mut_ptr ensures the returned pointer 182 | // is correctly aligned, and the if expression we are in ensures the buffer is 183 | // large enough. 184 | // 185 | // Because `future` is bound by `'a` and `StackFuture` is also bound by `'a`, 186 | // we can be sure anything that `future` closes over will also outlive `result`. 187 | unsafe { result.as_mut_ptr::().write(future) }; 188 | 189 | Ok(result) 190 | } else { 191 | Err(IntoStackFutureError::new::(future)) 192 | } 193 | } 194 | 195 | /// Creates a StackFuture from the given future, boxing if necessary 196 | /// 197 | /// This version will succeed even if the future is larger than `STACK_SIZE`. If the future 198 | /// is too large, `from_or_box` will allocate a `Box` on the heap and store the resulting 199 | /// boxed future in the `StackFuture`. 200 | /// 201 | /// The same thing also happens if the wrapped future's alignment is larger than StackFuture's 202 | /// alignment. 203 | /// 204 | /// This function requires the "alloc" crate feature. 205 | #[cfg(feature = "alloc")] 206 | pub fn from_or_box(future: F) -> Self 207 | where 208 | F: Future + Send + 'a, // the bounds here should match those in the _phantom field 209 | { 210 | Self::try_from(future).unwrap_or_else(|err| Self::from(Box::pin(err.into_inner()))) 211 | } 212 | 213 | /// A wrapper around the inner future's poll function, which we store in the poll_fn field 214 | /// of this struct. 215 | fn poll_inner(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 216 | self.as_pin_mut_ref::().poll(cx) 217 | } 218 | 219 | /// A wrapper around the inner future's drop function, which we store in the drop_fn field 220 | /// of this struct. 221 | fn drop_inner(&mut self) { 222 | // SAFETY: *this.as_mut_ptr() was previously written as type F 223 | unsafe { ptr::drop_in_place(self.as_mut_ptr::()) } 224 | } 225 | 226 | /// Returns a pointer into self.data that meets the alignment requirements for type `F` 227 | /// 228 | /// Before writing to the returned pointer, the caller must ensure that self.data is large 229 | /// enough to hold F and any required padding. 230 | fn as_mut_ptr(&mut self) -> *mut F { 231 | assert!(Self::has_space_for::()); 232 | // SAFETY: Self is laid out so that the space for the future comes at offset 0. 233 | // This is checked by an assertion in Self::from. Thus it's safe to cast a pointer 234 | // to Self into a pointer to the wrapped future. 235 | unsafe { mem::transmute(self) } 236 | } 237 | 238 | /// Returns a pinned mutable reference to a type F stored in self.data 239 | fn as_pin_mut_ref(self: Pin<&mut Self>) -> Pin<&mut F> { 240 | // SAFETY: `StackFuture` is only created by `StackFuture::from`, which 241 | // writes an `F` to `self.as_mut_ptr(), so it's okay to cast the `*mut F` 242 | // to an `&mut F` with the same lifetime as `self`. 243 | // 244 | // For pinning, since self is already pinned, we know the wrapped future 245 | // is also pinned. 246 | // 247 | // This function is only doing pointer arithmetic and casts, so we aren't moving 248 | // any pinned data. 249 | unsafe { self.map_unchecked_mut(|this| &mut *this.as_mut_ptr()) } 250 | } 251 | 252 | /// Computes how much space is required to store a value of type `F` 253 | const fn required_space() -> usize { 254 | mem::size_of::() 255 | } 256 | 257 | /// Determines whether this `StackFuture` can hold a value of type `F` 258 | pub const fn has_space_for() -> bool { 259 | Self::required_space::() <= STACK_SIZE 260 | } 261 | 262 | /// Determines whether this `StackFuture` can hold the referenced value 263 | pub const fn has_space_for_val(_: &F) -> bool { 264 | Self::has_space_for::() 265 | } 266 | 267 | /// Determines whether this `StackFuture`'s alignment is compatible with the 268 | /// type `F`. 269 | pub const fn has_alignment_for() -> bool { 270 | mem::align_of::() <= mem::align_of::() 271 | } 272 | 273 | /// Determines whether this `StackFuture`'s alignment is compatible with the 274 | /// referenced value. 275 | pub const fn has_alignment_for_val(_: &F) -> bool { 276 | Self::has_alignment_for::() 277 | } 278 | } 279 | 280 | impl<'a, T, const STACK_SIZE: usize> Future for StackFuture<'a, T, { STACK_SIZE }> { 281 | type Output = T; 282 | 283 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 284 | // SAFETY: This is doing pin projection. We unpin self so we can 285 | // access self.poll_fn, and then re-pin self to pass it into poll_in. 286 | // The part of the struct that needs to be pinned is data, since it 287 | // contains a potentially self-referential future object, but since we 288 | // do not touch that while self is unpinned and we do not move self 289 | // while unpinned we are okay. 290 | unsafe { 291 | let this = self.get_unchecked_mut(); 292 | (this.poll_fn)(Pin::new_unchecked(this), cx) 293 | } 294 | } 295 | } 296 | 297 | impl<'a, T, const STACK_SIZE: usize> Drop for StackFuture<'a, T, { STACK_SIZE }> { 298 | fn drop(&mut self) { 299 | (self.drop_fn)(self); 300 | } 301 | } 302 | 303 | struct AssertFits(PhantomData); 304 | 305 | impl AssertFits { 306 | const ASSERT: () = { 307 | if !StackFuture::::has_space_for::() { 308 | concat_panic!( 309 | "Future is too large: ", 310 | StackFuture::::required_space::(), 311 | " > ", 312 | STACK_SIZE 313 | ); 314 | } 315 | 316 | if !StackFuture::::has_alignment_for::() { 317 | concat_panic!( 318 | "Future has incompatible alignment: ", 319 | align_of::(), 320 | " > ", 321 | align_of::>() 322 | ); 323 | } 324 | }; 325 | } 326 | 327 | /// Captures information about why a future could not be converted into a [`StackFuture`] 328 | /// 329 | /// It also contains the original future so that callers can still run the future in error 330 | /// recovery paths, such as by boxing the future instead of wrapping it in [`StackFuture`]. 331 | pub struct IntoStackFutureError { 332 | /// The size of the StackFuture we tried to convert the future into 333 | maximum_size: usize, 334 | /// The StackFuture's alignment 335 | maximum_alignment: usize, 336 | /// The future that was attempted to be wrapped 337 | future: F, 338 | } 339 | 340 | impl IntoStackFutureError { 341 | fn new(future: F) -> Self { 342 | Self { 343 | maximum_size: mem::size_of::(), 344 | maximum_alignment: mem::align_of::(), 345 | future, 346 | } 347 | } 348 | 349 | /// Returns true if the target [`StackFuture`] was too small to hold the given future. 350 | pub fn insufficient_space(&self) -> bool { 351 | self.maximum_size < mem::size_of_val(&self.future) 352 | } 353 | 354 | /// Returns true if the target [`StackFuture`]'s alignment was too small to accommodate the given future. 355 | pub fn alignment_too_small(&self) -> bool { 356 | self.maximum_alignment < mem::align_of_val(&self.future) 357 | } 358 | 359 | /// Returns the alignment of the wrapped future. 360 | pub fn required_alignment(&self) -> usize { 361 | mem::align_of_val(&self.future) 362 | } 363 | 364 | /// Returns the size of the wrapped future. 365 | pub fn required_space(&self) -> usize { 366 | mem::size_of_val(&self.future) 367 | } 368 | 369 | /// Returns the alignment of the target [`StackFuture`], which is also the maximum alignment 370 | /// that can be wrapped. 371 | pub const fn available_alignment(&self) -> usize { 372 | self.maximum_alignment 373 | } 374 | 375 | /// Returns the amount of space that was available in the target [`StackFuture`]. 376 | pub const fn available_space(&self) -> usize { 377 | self.maximum_size 378 | } 379 | 380 | /// Returns the underlying future that caused this error 381 | /// 382 | /// Can be used to try again, either by directly awaiting the future, wrapping it in a `Box`, 383 | /// or some other method. 384 | fn into_inner(self) -> F { 385 | self.future 386 | } 387 | } 388 | 389 | impl Display for IntoStackFutureError { 390 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 391 | match (self.alignment_too_small(), self.insufficient_space()) { 392 | (true, true) => write!( 393 | f, 394 | "cannot create StackFuture, required size is {}, available space is {}; required alignment is {} but maximum alignment is {}", 395 | self.required_space(), 396 | self.available_space(), 397 | self.required_alignment(), 398 | self.available_alignment() 399 | ), 400 | (true, false) => write!( 401 | f, 402 | "cannot create StackFuture, required alignment is {} but maximum alignment is {}", 403 | self.required_alignment(), 404 | self.available_alignment() 405 | ), 406 | (false, true) => write!( 407 | f, 408 | "cannot create StackFuture, required size is {}, available space is {}", 409 | self.required_space(), 410 | self.available_space() 411 | ), 412 | // If we have space and alignment, then `try_from` would have succeeded 413 | (false, false) => unreachable!(), 414 | } 415 | } 416 | } 417 | 418 | impl Debug for IntoStackFutureError { 419 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 420 | f.debug_struct("IntoStackFutureError") 421 | .field("maximum_size", &self.maximum_size) 422 | .field("maximum_alignment", &self.maximum_alignment) 423 | .field("future", &core::any::type_name::()) 424 | .finish() 425 | } 426 | } 427 | 428 | #[cfg(test)] 429 | mod tests; 430 | --------------------------------------------------------------------------------