smol_macros/lib.rs
1//! Macros for using `smol-rs`.
2//!
3//! One of the advantages of [`smol`] is that it lets you set up your own executor, optimized for
4//! your own use cases. However, quick scaffolding is important for many organizational use cases.
5//! Especially when sane defaults are appreciated, setting up your own executor is a waste of
6//! time.
7//!
8//! This crate provides macros for setting up an efficient [`smol`] runtime quickly and
9//! effectively. It provides sane defaults that are useful for most applications.
10//!
11//! ## Simple Executor
12//!
13//! Just have an `async` main function, using the [`main`] macro.
14//!
15//!
16//! ```
17//! use smol_macros::main;
18//!
19//! main! {
20//! async fn main() {
21//! println!("Hello, world!");
22//! }
23//! }
24//! ```
25//!
26//! This crate uses declarative macros rather than procedural macros, in order to avoid needing
27//! to use heavy macro dependencies. If you want to use the proc macro syntax, you can use the
28//! [`macro_rules_attribute::apply`] function to emulate it.
29//!
30//! The following is equivalent to the previous example.
31//!
32//! ```
33//! use macro_rules_attribute::apply;
34//! use smol_macros::main;
35//!
36//! #[apply(main!)]
37//! async fn main() {
38//! println!("Hello, world!");
39//! }
40//! ```
41//!
42//! ## Task-Based Executor
43//!
44//! This crate re-exports [`smol::Executor`]. If that is used as the first parameter in a
45//! function in [`main`], it will automatically create the executor.
46//!
47//! ```
48//! use macro_rules_attribute::apply;
49//! use smol_macros::{main, Executor};
50//!
51//! #[apply(main!)]
52//! async fn main(ex: &Executor<'_>) {
53//! ex.spawn(async { println!("Hello world!"); }).await;
54//! }
55//! ```
56//!
57//! If the thread-safe [`smol::Executor`] is used here, a thread pool will be spawned to run
58//! the executor on multiple threads. For the thread-unsafe [`smol::LocalExecutor`], no threads
59//! will be spawned.
60//!
61//! See documentation for the [`main`] function for more details.
62//!
63//! ## Tests
64//!
65//! Use the [`test`] macro to set up test cases that run self-contained executors.
66//!
67//! ```
68//! use macro_rules_attribute::apply;
69//! use smol_macros::{test, Executor};
70//!
71//! #[apply(test!)]
72//! async fn do_test(ex: &Executor<'_>) {
73//! ex.spawn(async {
74//! assert_eq!(1 + 1, 2);
75//! }).await;
76//! }
77//! ```
78//!
79//! [`smol`]: https://crates.io/crates/smol
80//! [`smol::Executor`]: https://docs.rs/smol/latest/smol/struct.Executor.html
81//! [`smol::LocalExecutor`]: https://docs.rs/smol/latest/smol/struct.LocalExecutor.html
82//! [`macro_rules_attribute::apply`]: https://docs.rs/macro_rules_attribute/latest/macro_rules_attribute/attr.apply.html
83
84#![forbid(unsafe_code)]
85
86#[doc(no_inline)]
87pub use async_executor::{Executor, LocalExecutor};
88
89/// Turn a main function into one that runs inside of a self-contained executor.
90///
91/// The function created by this macro spawns an executor, spawns threads to run that executor
92/// on (if applicable), and then blocks the current thread on the future.
93///
94/// ## Examples
95///
96/// Like [`tokio::main`], this function is not limited to wrapping the program's entry point.
97/// In a mostly synchronous program, it can wrap a self-contained `async` function in its
98/// own executor.
99///
100/// ```
101/// use macro_rules_attribute::apply;
102/// use smol_macros::{main, Executor};
103///
104/// fn do_something_sync() -> u32 {
105/// 1 + 1
106/// }
107///
108/// #[apply(main!)]
109/// async fn do_something_async(ex: &Executor<'_>) -> u32 {
110/// ex.spawn(async { 1 + 1 }).await
111/// }
112///
113/// fn main() {
114/// let x = do_something_sync();
115/// let y = do_something_async();
116/// assert_eq!(x + y, 4);
117/// }
118/// ```
119///
120/// The first parameter to the `main` function can be an executor. It can be one of the following:
121///
122/// - Nothing.
123/// - `&`[`Executor`]
124/// - `&`[`LocalExecutor`]
125/// - `Arc<`[`Executor`]`>`
126/// - `Rc<`[`LocalExecutor`]`>`
127///
128/// [`tokio::main`]: https://docs.rs/tokio/latest/tokio/attr.main.html
129/// [`Executor`]: https://docs.rs/smol/latest/smol/struct.Executor.html
130/// [`LocalExecutor`]: https://docs.rs/smol/latest/smol/struct.LocalExecutor.html
131#[macro_export]
132macro_rules! main {
133 (
134 $(#[$attr:meta])*
135 async fn $name:ident () $(-> $ret:ty)? $bl:block
136 ) => {
137 $(#[$attr])*
138 fn $name () $(-> $ret)? {
139 $crate::__private::block_on(async {
140 $bl
141 })
142 }
143 };
144
145 (
146 $(#[$post_attr:meta])*
147 async fn $name:ident ($ex:ident : & $exty:ty)
148 $(-> $ret:ty)? $bl:block
149 ) => {
150 $(#[$post_attr])*
151 fn $name () $(-> $ret)? {
152 <$exty as $crate::__private::MainExecutor>::with_main(|ex| {
153 $crate::__private::block_on(ex.run(async move {
154 let $ex = ex;
155 $bl
156 }))
157 })
158 }
159 };
160
161 (
162 $(#[$post_attr:meta])*
163 async fn $name:ident ($ex:ident : $exty:ty)
164 $(-> $ret:ty)? $bl:block
165 ) => {
166 $crate::main! {
167 $(#[$post_attr])*
168 async fn $name(ex: &$exty) $(-> $ret)? {
169 let $ex = ex.clone();
170 $bl
171 }
172 }
173 }
174}
175
176/// Wrap a test in an asynchronous executor.
177///
178/// This is equivalent to the [`main`] macro, but adds the `#[test]` attribute.
179///
180/// ## Examples
181///
182/// ```
183/// use macro_rules_attribute::apply;
184/// use smol_macros::test;
185///
186/// #[apply(test!)]
187/// async fn do_test() {
188/// assert_eq!(1 + 1, 2);
189/// }
190/// ```
191#[macro_export]
192macro_rules! test {
193 // Special case to get around bug in macro engine.
194 (
195 $(#[$post_attr:meta])*
196 async fn $name:ident ($exname:ident : & $exty:ty)
197 $(-> $ret:ty)? $bl:block
198 ) => {
199 $crate::main! {
200 $(#[$post_attr])*
201 #[core::prelude::v1::test]
202 async fn $name($exname: &$exty) $(-> $ret)? $bl
203 }
204 };
205
206 (
207 $(#[$post_attr:meta])*
208 async fn $name:ident ($($pname:ident : $pty:ty),* $(,)?)
209 $(-> $ret:ty)? $bl:block
210 ) => {
211 $crate::main! {
212 $(#[$post_attr])*
213 #[core::prelude::v1::test]
214 async fn $name($($pname: $pty),*) $(-> $ret)? $bl
215 }
216 };
217}
218
219#[doc(hidden)]
220pub mod __private {
221 pub use async_io::block_on;
222 pub use std::rc::Rc;
223
224 use crate::{Executor, LocalExecutor};
225 use event_listener::Event;
226 use std::sync::atomic::{AtomicBool, Ordering};
227 use std::sync::Arc;
228 use std::thread;
229
230 /// Something that can be set up as an executor.
231 #[doc(hidden)]
232 pub trait MainExecutor: Sized {
233 /// Create this type and pass it into `main`.
234 fn with_main<T, F: FnOnce(&Self) -> T>(f: F) -> T;
235 }
236
237 impl MainExecutor for Arc<Executor<'_>> {
238 #[inline]
239 fn with_main<T, F: FnOnce(&Self) -> T>(f: F) -> T {
240 let ex = Arc::new(Executor::new());
241 with_thread_pool(&ex, || f(&ex))
242 }
243 }
244
245 impl MainExecutor for Executor<'_> {
246 #[inline]
247 fn with_main<T, F: FnOnce(&Self) -> T>(f: F) -> T {
248 let ex = Executor::new();
249 with_thread_pool(&ex, || f(&ex))
250 }
251 }
252
253 impl MainExecutor for Rc<LocalExecutor<'_>> {
254 #[inline]
255 fn with_main<T, F: FnOnce(&Self) -> T>(f: F) -> T {
256 f(&Rc::new(LocalExecutor::new()))
257 }
258 }
259
260 impl MainExecutor for LocalExecutor<'_> {
261 fn with_main<T, F: FnOnce(&Self) -> T>(f: F) -> T {
262 f(&LocalExecutor::new())
263 }
264 }
265
266 /// Run a function that takes an `Executor` inside of a thread pool.
267 #[inline]
268 fn with_thread_pool<T>(ex: &Executor<'_>, f: impl FnOnce() -> T) -> T {
269 let stopper = WaitForStop::new();
270
271 // Create a thread for each CPU.
272 thread::scope(|scope| {
273 let num_threads = thread::available_parallelism().map_or(1, |num| num.get());
274 for i in 0..num_threads {
275 let ex = &ex;
276 let stopper = &stopper;
277
278 thread::Builder::new()
279 .name(format!("smol-macros-{i}"))
280 .spawn_scoped(scope, || {
281 block_on(ex.run(stopper.wait()));
282 })
283 .expect("failed to spawn thread");
284 }
285
286 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f));
287
288 stopper.stop();
289
290 match result {
291 Ok(value) => value,
292 Err(err) => std::panic::resume_unwind(err),
293 }
294 })
295 }
296
297 /// Wait for the executor to stop.
298 struct WaitForStop {
299 /// Whether or not we need to stop.
300 stopped: AtomicBool,
301
302 /// Wait for the stop.
303 events: Event,
304 }
305
306 impl WaitForStop {
307 /// Create a new wait for stop.
308 #[inline]
309 fn new() -> Self {
310 Self {
311 stopped: AtomicBool::new(false),
312 events: Event::new(),
313 }
314 }
315
316 /// Wait for the event to stop.
317 #[inline]
318 async fn wait(&self) {
319 loop {
320 if self.stopped.load(Ordering::Relaxed) {
321 return;
322 }
323
324 event_listener::listener!(&self.events => listener);
325
326 if self.stopped.load(Ordering::Acquire) {
327 return;
328 }
329
330 listener.await;
331 }
332 }
333
334 /// Stop the waiter.
335 #[inline]
336 fn stop(&self) {
337 self.stopped.store(true, Ordering::SeqCst);
338 self.events.notify_additional(std::usize::MAX);
339 }
340 }
341}