Skip to main content

churust_macros/
lib.rs

1//! Procedural macros for the [Churust](https://docs.rs/churust) web framework.
2//!
3//! This crate exposes a single attribute macro, [`main`], which turns an
4//! `async fn main()` into a synchronous entry point backed by a multi-threaded
5//! Tokio runtime.  You should **not** depend on this crate directly; instead
6//! use the top-level `churust` crate and write `#[churust::main]`.
7//!
8//! # Quick start
9//!
10//! In your application's `main.rs`, after adding `churust` as a dependency:
11//!
12//! ```text
13//! #[churust::main]
14//! async fn main() -> std::io::Result<()> {
15//!     // Churust::server()…start().await goes here.
16//!     Ok(())
17//! }
18//! ```
19//!
20//! The examples in this crate are illustrations rather than doctests: the
21//! generated code names `::churust`, which cannot resolve from inside
22//! `churust-macros` because `churust` depends on this crate and not the
23//! reverse. Executable coverage lives in `churust/tests/macro_main.rs`.
24//!
25//! The attribute strips the `async` modifier and injects a Tokio runtime so
26//! that the Rust compiler sees an ordinary synchronous `fn main()`.  This
27//! mirrors the ergonomics of `#[tokio::main]` while keeping the framework's
28//! entry-point convention explicit and framework-branded.
29//!
30//! # When to use this crate directly
31//!
32//! Almost never.  The re-export via `churust::main` (or `churust::prelude::*`)
33//! is the intended consumption path.  The only reason to take a direct
34//! dependency on `churust-macros` is if you are building a crate that wraps or
35//! extends Churust's attribute macros at a low level.
36
37#![deny(missing_docs)]
38
39use proc_macro::TokenStream;
40use quote::quote;
41use syn::{parse_macro_input, ItemFn};
42
43/// Turns `async fn main()` into a synchronous entry point that runs on a
44/// multi-threaded Tokio runtime.
45///
46/// Apply this attribute to your application's `main` function in place of
47/// `#[tokio::main]`.  The attribute:
48///
49/// 1. Verifies that the annotated function is `async`.  A compile error is
50///    emitted if it is not.
51/// 2. Removes the `async` keyword so the result is a valid synchronous `fn
52///    main` that the Rust runtime can call directly.
53/// 3. Wraps the original function body in a call to
54///    `tokio::runtime::Builder::new_multi_thread().enable_all().build()` and
55///    then blocks on the async body with `block_on`.
56///
57/// # Constraints
58///
59/// * The annotated function **must** be `async`.  Applying the attribute to a
60///   non-async function is a hard compile error:
61///
62///   ```text
63///   error: #[churust::main] requires an `async fn`
64///   ```
65///
66/// * `tokio` must be a dependency (directly or transitively) of the crate that
67///   uses this attribute, because the expanded code references
68///   `::tokio::runtime::Builder`.
69///
70/// # Return type
71///
72/// The return type is preserved unchanged.  Returning `std::io::Result<()>` is
73/// idiomatic because `Churust::server().start().await` returns that type:
74///
75/// ```text
76/// #[churust::main]
77/// async fn main() -> std::io::Result<()> {
78///     Ok(())
79/// }
80/// ```
81///
82/// Returning `()` is also valid when no top-level I/O error needs to be
83/// propagated:
84///
85/// ```text
86/// #[churust::main]
87/// async fn main() {
88///     // fire-and-forget setup, panics on failure
89/// }
90/// ```
91///
92/// # Attribute arguments
93///
94/// No arguments are accepted.  The attribute token stream is intentionally
95/// ignored so that the macro does not trap future extensions, but passing any
96/// tokens currently has no effect.
97///
98/// # Generated code
99///
100/// Given the following input:
101///
102/// ```text
103/// #[churust::main]
104/// async fn main() -> std::io::Result<()> {
105///     do_something().await
106/// }
107/// ```
108///
109/// The macro expands to roughly the following synchronous function.  The
110/// generated runtime variable uses a mangled name (`__rt`) to avoid shadowing
111/// anything in the caller's scope:
112///
113/// ```text
114/// fn main() -> std::io::Result<()> {
115///     let __rt = ::churust::__private::tokio::runtime::Builder::new_multi_thread()
116///         .enable_all()
117///         .build()
118///         .expect("failed to build tokio runtime");
119///     __rt.block_on(async move {
120///         do_something().await
121///     })
122/// }
123/// ```
124///
125/// The runtime is reached through `churust`'s re-export rather than `::tokio`,
126/// so an application needs only `churust` as a dependency.
127///
128/// All original attributes (e.g. `#[cfg(…)]`, doc comments) and visibility
129/// modifiers on the original function are forwarded to the generated function
130/// unchanged.
131#[proc_macro_attribute]
132pub fn main(_attr: TokenStream, item: TokenStream) -> TokenStream {
133    let input = parse_macro_input!(item as ItemFn);
134
135    if input.sig.asyncness.is_none() {
136        return syn::Error::new_spanned(
137            input.sig.fn_token,
138            "#[churust::main] requires an `async fn`",
139        )
140        .to_compile_error()
141        .into();
142    }
143
144    let attrs = &input.attrs;
145    let vis = &input.vis;
146    let sig = &input.sig;
147    let body = &input.block;
148    let output = &sig.output;
149    let ident = &sig.ident;
150
151    // Rebuild a non-async signature with the same name/return type.
152    let expanded = quote! {
153        #(#attrs)*
154        #vis fn #ident() #output {
155            let __rt = ::churust::__private::tokio::runtime::Builder::new_multi_thread()
156                .enable_all()
157                .build()
158                .expect("failed to build tokio runtime");
159            __rt.block_on(async move #body)
160        }
161    };
162
163    expanded.into()
164}