act_zero_macro/lib.rs
1//! # act-zero-macro
2//!
3//! Procedural macros for the `act-zero` crate.
4#![deny(missing_docs)]
5
6use proc_macro::TokenStream;
7use proc_macro2::TokenStream as TokenStream2;
8
9mod common;
10mod expand_impl;
11mod expand_trait;
12mod receiver;
13mod respan;
14
15/// # The `#[act_zero]` macro.
16///
17/// This macro is used in two locations, which will be documented separately:
18///
19/// ## Trait position
20///
21/// ```nocompile
22/// #[act_zero]
23/// trait Greeter {
24/// fn greet(&self, name: String, res: Sender<String>);
25/// }
26/// ```
27///
28/// This is used to define an *actor trait*. Actor traits (much like normal traits) define a
29/// set of methods to be implemented by the actor type. However, there are a number of
30/// constraints:
31/// - Methods must take `&self` as the receiver.
32/// - All argument types must be `Send + 'static`.
33/// - Generic methods must have a `where Self: Sized` bound.
34/// - There must be no return type.
35/// - If the last argument is named `res`, it must be of type `Sender<_>`.
36///
37/// These last two constraints are related: actor systems work by passing messages and
38/// processing them asynchronously. Any return value would not be immediately available,
39/// so instead the caller passes in a `Sender<_>` to receive the result.
40///
41/// As a convenience, there will be an asynchronous `call_xyz()` method to hide this
42/// complexity from the caller:
43/// ```nocompile
44/// let addr: Addr<dyn Greeter> = ...;
45/// let res = addr.call_greet("John".into()).await?;
46/// ```
47///
48/// Internally, this macro defines several additional items:
49/// - `trait GreeterImpl`
50/// You will never use this trait directly, but if you want your trait to be implementable
51/// by downstream crates, you must export this trait under the same path as the actor trait
52/// itself.
53/// - `enum GreeterMsg`
54/// This enum contains a variant for each object-safe method in the trait. It is used in
55/// remoting scenarios when messages may need to be trasferred between processes or over
56/// the network, but is otherwise not used.
57/// - `trait GreeterExt`
58/// This trait is implemented for the `Addr` and `WeakAddr` types when they contain a type
59/// implementing the actor trait. Methods on this trait are the primary way you would
60/// interact with the actor, and this is where helper methods like `call_greeter` are
61/// defined.
62///
63/// ## Trait implementation position
64///
65/// ```nocompile
66/// #[act_zero]
67/// impl Greeter for SimpleGreeter {
68/// async fn greet(&mut self, name: String, res: Sender<String>) {
69/// self.number_of_greets += 1;
70/// res.send(format!(
71/// "Hello, {}. You are number {}!",
72/// name, self.number_of_greets
73/// ))
74/// .ok();
75/// }
76/// }
77/// ```
78///
79/// This is used to implement an actor trait for an actor. The signature of the methods
80/// in the implementation differ substantially from those signature in the trait definition:
81///
82/// - All methods must be `async`. This may be optional in future.
83/// - Methods can return any type implementing `IntoResult`. If an error variant is returned
84/// it will be passed to the appropriate error handler implemented for the actor.
85/// - The receiver type can be any of:
86///
87/// 1) `&mut self`
88/// The method will run to completion with exclusive access to the actor state.
89/// No methods taking `&self` or `&mut self` can run concurrently. Long running
90/// methods taking `&mut self` should be avoided if you want your actor to stay
91/// responsive to new messages.
92///
93/// 2) `&self`
94/// The method will run to completion with shared access to the actor state.
95/// Other methods taking `&self` can run concurrently, but methods taking
96/// `&mut self` cannot.
97///
98/// 3) `self: Addr<Local<Self>>`
99/// The method has no direct access to the actor state, but can run concurrently
100/// with any other methods, including those taking `&mut self`. This can be useful
101/// for implementing delayed or timed events without blocking the actor.
102/// Access to the actor state can be achieved by awaiting another method on the
103/// actor to do the update.
104///
105/// No additional items are generated by the expansion of this variant of the macro.
106#[proc_macro_attribute]
107pub fn act_zero(_attr: TokenStream, item: TokenStream) -> TokenStream {
108 let res = match act_zero_impl(item) {
109 Ok(tokens) => tokens,
110 Err(e) => e.to_compile_error(),
111 };
112 res.into()
113}
114
115fn act_zero_impl(item: TokenStream) -> syn::Result<TokenStream2> {
116 let item: syn::Item = syn::parse(item)?;
117 Ok(match item {
118 syn::Item::Trait(trait_item) => expand_trait::expand(trait_item)?,
119 syn::Item::Impl(impl_item) => expand_impl::expand(impl_item)?,
120 _ => {
121 return Err(syn::Error::new_spanned(
122 item,
123 "Expected a trait or a trait implementation",
124 ))
125 }
126 })
127}