Skip to content

Instantly share code, notes, and snippets.

@malcolmgreaves
Created April 2, 2026 23:07
Show Gist options
  • Select an option

  • Save malcolmgreaves/ebdb637b573819305a125ae649171c91 to your computer and use it in GitHub Desktop.

Select an option

Save malcolmgreaves/ebdb637b573819305a125ae649171c91 to your computer and use it in GitHub Desktop.
`scoped_async!` macro for evaluating an `async` expression that borrows non-static data.
use async_scoped::TokioScope;
use futures::stream::{self, StreamExt};
use std::time::Duration;
/// Evaluates an async expression that can borrow non-static data.
/// All borrowed data must live for the lifetime of the scope.
macro_rules! scoped_async {
(|$scope:ident| $body:expr) => {{
// SAFETY: the `scope_and_collect` future **MUST** be awaited before being dropped
let (result, _) = unsafe {
TokioScope::scope_and_collect(|$scope| $body)
}
.await;
// SAFETY: we have awaited the future and we have not dropped it yet
result
}};
}
async fn process(item: &str) -> String {
tokio::time::sleep(Duration::from_millis(50)).await;
format!("{}-processed", item)
}
/// Example use of scoped_async!
#[tokio::main]
async fn main() {
let data: Vec<String> = vec![
"alpha".into(),
"beta".into(),
"gamma".into(),
"delta".into(),
];
let processed: Vec<String> = scoped_async!(|scope| {
scope
.spawn(async {
// we're borrowing the elements of `data` here!
stream::iter(data.iter())
.map(|item| async move { process(item).await })
.buffer_unordered(4)
.collect::<Vec<_>>()
.await
})
.await
})
// Result<the scope.spawn() async block, JoinError>
.unwrap();
println!("{:?}", processed);
}
/* Use the following Cargo.toml:
[package]
name = "scoped-async-example"
version = "0.1.0"
edition = "2024"
[dependencies]
async-scoped = { version = "0.9", features = ["use-tokio"] }
futures = "0.3"
tokio = { version = "1", features = ["full"] }
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment