dsl_ractor/
lib.rs

1mod ast;
2mod expand;
3mod kw;
4mod parse;
5mod validate;
6
7use expand::expand;
8use parse::actor_args::parse_actor_args;
9use proc_macro::TokenStream;
10use proc_macro2::TokenStream as TokenStream2;
11use syn::{DeriveInput, parse_macro_input, spanned::Spanned};
12use validate::args::validate_actor_args;
13
14/// Parses input as either a `Block` or an `Expr`.
15/// Falls back to wrapping the `Expr` in a block if no block is found.
16macro_rules! parse_block_or_expr {
17    ($input:expr) => {
18        if let Ok(block) = syn::parse::<syn::Block>($input.clone()) {
19            block
20        } else {
21            let expr: syn::Expr = syn::parse($input).expect("expected block or expression");
22            syn::parse_quote!({ #expr })
23        }
24    };
25}
26
27/// Attribute macro to define an `Actor`.
28///
29/// This macro dramatically reduces boilerplate when implementing the `ractor::Actor` trait.
30/// It automatically generates the trait implementation with proper type definitions and
31/// method signatures, delegating to user-defined helper methods.
32///
33/// # Arguments
34///
35/// The macro accepts three named arguments:
36/// - `msg`: The message type your actor will receive
37/// - `state`: The state type your actor will maintain
38/// - `args`: The arguments type used to initialize your actor (defaults to `()`)
39///
40/// # Example
41///
42/// ```rust,ignore
43/// use dsl_ractor::actor;
44/// use ractor::Actor;
45///
46/// #[derive(Debug, Clone)]
47/// enum CounterMsg {
48///     Increment,
49///     Print,
50/// }
51///
52/// #[actor(msg = CounterMsg, state = i32, args = i32)]
53/// struct CounterActor;
54/// ```
55///
56/// # What This Generates (and why helpers exist)
57///
58/// The macro expands into a complete `Actor` trait implementation:
59/// - The trait methods delegate to helper methods on your type.
60/// - You provide those helpers via the [`actor_pre_start!`] and [`actor_handle!`] macros.
61///
62/// The helpers are required because a proc macro attached to a struct cannot also inject
63/// items into a separate `impl Actor for ...` block. Delegation keeps the API ergonomic
64/// while satisfying Rust's macro hygiene rules.
65///
66/// ```rust,ignore
67/// // Your original struct definition
68/// struct CounterActor;
69///
70/// // Generated code (simplified):
71/// #[async_trait]  // only with "async-trait" feature
72/// impl Actor for CounterActor {
73///     type Msg = CounterMsg;
74///     type State = i32;
75///     type Arguments = i32;
76///
77///     async fn pre_start(
78///         &self,
79///         myself: ActorRef<Self::Msg>,
80///         args: Self::Arguments
81///     ) -> Result<Self::State, ActorProcessingErr> {
82///         self.on_start(myself, args).await
83///     }
84///
85///     async fn handle(
86///         &self,
87///         myself: ActorRef<Self::Msg>,
88///         msg: Self::Msg,
89///         state: &mut Self::State
90///     ) -> Result<(), ActorProcessingErr> {
91///         self.handle_msg(myself, msg, state).await
92///     }
93/// }
94/// ```
95///
96/// # Comparison: Before vs After
97///
98/// **Without this macro (raw Ractor):**
99/// ```rust,ignore
100/// use ractor::{Actor, ActorProcessingErr, ActorRef, async_trait};
101///
102/// #[derive(Debug, Clone)]
103/// enum CounterMsg {
104///     Increment,
105///     Print,
106/// }
107///
108/// struct CounterActor;
109///
110/// #[async_trait]
111/// impl Actor for CounterActor {
112///     type Msg = CounterMsg;
113///     type State = i32;
114///     type Arguments = i32;
115///
116///     async fn pre_start(
117///         &self,
118///         _myself: ActorRef<Self::Msg>,
119///         args: Self::Arguments,
120///     ) -> Result<Self::State, ActorProcessingErr> {
121///         Ok(args)  // 30+ lines of boilerplate just to get here!
122///     }
123///
124///     async fn handle(
125///         &self,
126///         _myself: ActorRef<Self::Msg>,
127///         message: Self::Msg,
128///         state: &mut Self::State,
129///     ) -> Result<(), ActorProcessingErr> {
130///         match message {
131///             CounterMsg::Increment => {
132///                 *state += 1;
133///                 Ok(())
134///             }
135///             CounterMsg::Print => {
136///                 println!("Count: {}", state);
137///                 Ok(())
138///             }
139///         }
140///     }
141/// }
142/// ```
143///
144/// **With this macro:**
145/// ```rust,ignore
146/// use dsl_ractor::{actor, actor_pre_start, actor_handle};
147///
148/// #[derive(Debug, Clone)]
149/// enum CounterMsg {
150///     Increment,
151///     Print,
152/// }
153///
154/// #[actor(msg = CounterMsg, state = i32, args = i32)]
155/// struct CounterActor;
156///
157/// impl CounterActor {
158///     actor_pre_start!(Ok(args));
159///
160///     actor_handle!({
161///         match msg {
162///             CounterMsg::Increment => {
163///                 *state += 1;
164///                 Ok(())
165///             }
166///             CounterMsg::Print => {
167///                 println!("Count: {}", state);
168///                 Ok(())
169///             }
170///         }
171///     });
172/// }
173/// ```
174///
175/// Notice how the macro:
176/// - Eliminates the need to write `impl Actor for ...`
177/// - No need to manually define associated types
178/// - No need to write method signatures with complex return types
179/// - Just focus on your actor's logic!
180///
181/// # Feature Flags
182///
183/// - `async-trait`: When enabled, uses the `async_trait` crate for trait methods.
184///   When disabled, uses `impl Future` return type position impl trait (RPIT).
185///
186/// # See Also
187///
188/// - [`actor_pre_start!`] - Macro for defining the initialization logic
189/// - [`actor_handle!`] - Macro for defining message handling logic
190#[proc_macro_attribute]
191pub fn actor(attr: TokenStream, item: TokenStream) -> TokenStream {
192    let input = parse_macro_input!(item as DeriveInput);
193    let attr_ts: TokenStream2 = attr.into();
194    let out = || -> syn::Result<_> {
195        let raw = parse_actor_args(input.span(), attr_ts)?;
196        let val = validate_actor_args(raw)?;
197        Ok(expand(&input, &val))
198    }();
199
200    match out {
201        Ok(ts) => ts.into(),
202        Err(e) => e.to_compile_error().into(),
203    }
204}
205
206/// Procedural macro to define the actor's initialization logic.
207///
208/// This macro generates the `on_start()` helper method that is called by the
209/// `Actor::pre_start()` trait method generated by [`#[actor]`](macro@actor).
210///
211/// # Purpose
212///
213/// The `pre_start` phase is where you initialize your actor's state from the
214/// provided arguments. This happens once when the actor is spawned, before it
215/// starts processing messages.
216///
217/// # Arguments
218///
219/// Accepts either a block or expression that evaluates to:
220/// `Result<State, ActorProcessingErr>`
221///
222/// The following variables are available in scope:
223/// - `myself`: `ActorRef<Self::Msg>` - Reference to this actor
224/// - `args`: `Self::Arguments` - The arguments passed to `Actor::spawn()`
225///
226/// # Example
227///
228/// ```rust,ignore
229/// use dsl_ractor::{actor, actor_pre_start, actor_handle};
230///
231/// #[actor(msg = String, state = Vec<String>, args = usize)]
232/// struct LogActor;
233///
234/// impl LogActor {
235///     // Simple expression: initialize empty vec with capacity
236///     actor_pre_start!(Ok(Vec::with_capacity(args)));
237///
238///     // Or use a block for more complex initialization:
239///     actor_pre_start!({
240///         println!("Actor {:?} starting with capacity {}", myself, args);
241///         let buffer = Vec::with_capacity(args);
242///         Ok(buffer)
243///     });
244///
245///     actor_handle!({
246///         state.push(msg);
247///         Ok(())
248///     });
249/// }
250/// ```
251///
252/// # What This Generates
253///
254/// **Your code:**
255/// ```rust,ignore
256/// impl MyActor {
257///     actor_pre_start!(Ok(args));
258/// }
259/// ```
260///
261/// **Expands to (with `async-trait` feature):**
262/// ```rust,ignore
263/// impl MyActor {
264///     pub async fn on_start(
265///         &self,
266///         myself: ractor::ActorRef<<Self as ractor::Actor>::Msg>,
267///         args: <Self as ractor::Actor>::Arguments,
268///     ) -> Result<<Self as ractor::Actor>::State, ractor::ActorProcessingErr> {
269///         Ok(args)
270///     }
271/// }
272/// ```
273///
274/// **Expands to (without `async-trait` feature):**
275/// ```rust,ignore
276/// impl MyActor {
277///     pub fn on_start(
278///         &self,
279///         myself: ractor::ActorRef<<Self as ractor::Actor>::Msg>,
280///         args: <Self as ractor::Actor>::Arguments,
281///     ) -> impl Future<Output = Result<<Self as ractor::Actor>::State, ractor::ActorProcessingErr>> + Send {
282///         async move {
283///             Ok(args)
284///         }
285///     }
286/// }
287/// ```
288///
289/// # Comparison: Before vs After
290///
291/// **Without this macro (raw Ractor):**
292/// ```rust,ignore
293/// #[async_trait]
294/// impl Actor for MyActor {
295///     type Msg = String;
296///     type State = Vec<String>;
297///     type Arguments = usize;
298///
299///     async fn pre_start(
300///         &self,
301///         myself: ActorRef<Self::Msg>,
302///         args: Self::Arguments,
303///     ) -> Result<Self::State, ActorProcessingErr> {
304///         // Finally! Your actual logic:
305///         Ok(Vec::with_capacity(args))
306///     }
307///     // ... handle method ...
308/// }
309/// ```
310///
311/// **With this macro:**
312/// ```rust,ignore
313/// #[actor(msg = String, state = Vec<String>, args = usize)]
314/// struct MyActor;
315///
316/// impl MyActor {
317///     actor_pre_start!(Ok(Vec::with_capacity(args)));
318///     // ... actor_handle! ...
319/// }
320/// ```
321///
322/// Reduces **~15 lines** of boilerplate to **1 line** of actual logic!
323///
324/// # Architecture Note
325///
326/// This macro generates a helper method `on_start()` rather than the trait method
327/// `pre_start()` directly because Rust proc macros running inside `impl MyActor`
328/// cannot modify the separate `impl Actor for MyActor` block generated by `#[actor]`.
329/// The trait method delegates to this helper method to bridge the gap.
330///
331/// # See Also
332///
333/// - [`#[actor]`](macro@actor) - Must be used first to set up the trait impl
334/// - [`actor_handle!`] - For defining message handling logic
335#[proc_macro]
336pub fn actor_pre_start(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
337    let body = parse_block_or_expr!(input);
338
339    #[cfg(feature = "async-trait")]
340    let tokens = quote::quote! {
341        pub async fn on_start(
342            &self,
343            myself: ractor::ActorRef<<Self as ractor::Actor>::Msg>,
344            args: <Self as ractor::Actor>::Arguments,
345        ) -> ::core::result::Result<(<Self as ractor::Actor>::State), ractor::ActorProcessingErr> {
346            #body
347        }
348    };
349
350    #[cfg(not(feature = "async-trait"))]
351    let tokens = quote::quote! {
352        pub fn on_start(
353            &self,
354            myself: ractor::ActorRef<<Self as ractor::Actor>::Msg>,
355            args: <Self as ractor::Actor>::Arguments,
356        ) -> impl ::core::future::Future<
357            Output=::core::result::Result<(<Self as ractor::Actor>::State), ractor::ActorProcessingErr>
358        > + Send {
359            async move {
360                #body
361            }
362        }
363    };
364
365    tokens.into()
366}
367
368/// Procedural macro to define the actor's message handling logic.
369///
370/// This macro generates the `handle_msg()` helper method that is called by the
371/// `Actor::handle()` trait method generated by [`#[actor]`](macro@actor).
372///
373/// # Purpose
374///
375/// This is the core of your actor - the logic that processes incoming messages
376/// and updates state. This method is called repeatedly for each message the actor
377/// receives during its lifetime.
378///
379/// # Arguments
380///
381/// Accepts either a block or expression that evaluates to:
382/// `Result<(), ActorProcessingErr>`
383///
384/// The following variables are available in scope:
385/// - `myself`: `ActorRef<Self::Msg>` - Reference to this actor (for sending messages to self)
386/// - `msg`: `Self::Msg` - The incoming message to process
387/// - `state`: `&mut Self::State` - Mutable reference to the actor's state
388///
389/// # Example
390///
391/// ```rust,ignore
392/// use dsl_ractor::{actor, actor_pre_start, actor_handle};
393/// use ractor::cast;
394///
395/// #[derive(Debug)]
396/// enum CounterMsg {
397///     Increment,
398///     Decrement,
399///     Reset,
400///     Print,
401/// }
402///
403/// #[actor(msg = CounterMsg, state = i32, args = i32)]
404/// struct CounterActor;
405///
406/// impl CounterActor {
407///     actor_pre_start!(Ok(args));
408///
409///     // Pattern match on messages and mutate state
410///     actor_handle!({
411///         match msg {
412///             CounterMsg::Increment => {
413///                 *state += 1;
414///                 println!("Counter: {}", state);
415///                 Ok(())
416///             }
417///             CounterMsg::Decrement => {
418///                 *state -= 1;
419///                 Ok(())
420///             }
421///             CounterMsg::Reset => {
422///                 *state = 0;
423///                 // Send messages to self
424///                 cast!(myself, CounterMsg::Print)?;
425///                 Ok(())
426///             }
427///             CounterMsg::Print => {
428///                 println!("Current value: {}", state);
429///                 Ok(())
430///             }
431///         }
432///     });
433/// }
434/// ```
435///
436/// # What This Generates
437///
438/// **Your code:**
439/// ```rust,ignore
440/// impl MyActor {
441///     actor_handle!({
442///         match msg {
443///             Msg::Ping => Ok(()),
444///             Msg::Stop => myself.stop(None),
445///         }
446///     });
447/// }
448/// ```
449///
450/// **Expands to (with `async-trait` feature):**
451/// ```rust,ignore
452/// impl MyActor {
453///     pub async fn handle_msg(
454///         &self,
455///         myself: ractor::ActorRef<<Self as ractor::Actor>::Msg>,
456///         msg: <Self as ractor::Actor>::Msg,
457///         state: &mut <Self as ractor::Actor>::State,
458///     ) -> Result<(), ractor::ActorProcessingErr> {
459///         match msg {
460///             Msg::Ping => Ok(()),
461///             Msg::Stop => myself.stop(None),
462///         }
463///     }
464/// }
465/// ```
466///
467/// **Expands to (without `async-trait` feature):**
468/// ```rust,ignore
469/// impl MyActor {
470///     pub fn handle_msg(
471///         &self,
472///         myself: ractor::ActorRef<<Self as ractor::Actor>::Msg>,
473///         msg: <Self as ractor::Actor>::Msg,
474///         state: &mut <Self as ractor::Actor>::State,
475///     ) -> impl Future<Output = Result<(), ractor::ActorProcessingErr>> + Send {
476///         async move {
477///             match msg {
478///                 Msg::Ping => Ok(()),
479///                 Msg::Stop => myself.stop(None),
480///             }
481///         }
482///     }
483/// }
484/// ```
485///
486/// # Comparison: Before vs After
487///
488/// **Without this macro (raw Ractor):**
489/// ```rust,ignore
490/// #[async_trait]
491/// impl Actor for PingPongActor {
492///     type Msg = PingPongMsg;
493///     type State = usize;
494///     type Arguments = usize;
495///
496///     async fn pre_start(
497///         &self,
498///         _myself: ActorRef<Self::Msg>,
499///         args: Self::Arguments,
500///     ) -> Result<Self::State, ActorProcessingErr> {
501///         Ok(args)
502///     }
503///
504///     async fn handle(
505///         &self,
506///         myself: ActorRef<Self::Msg>,
507///         message: Self::Msg,
508///         state: &mut Self::State,
509///     ) -> Result<(), ActorProcessingErr> {
510///         //Your actual message handling logic:
511///         match message {
512///             PingPongMsg::Ping => {
513///                 println!("Ping! Count: {}", state);
514///                 *state -= 1;
515///                 if *state > 0 {
516///                     cast!(myself, PingPongMsg::Pong)?;
517///                 }
518///                 Ok(())
519///             }
520///             PingPongMsg::Pong => {
521///                 println!("Pong! Count: {}", state);
522///                 cast!(myself, PingPongMsg::Ping)?;
523///                 Ok(())
524///             }
525///         }
526///     }
527/// }
528/// ```
529///
530/// **With this macro:**
531/// ```rust,ignore
532/// #[actor(msg = PingPongMsg, state = usize, args = usize)]
533/// struct PingPongActor;
534///
535/// impl PingPongActor {
536///     actor_pre_start!(Ok(args));
537///
538///     actor_handle!({
539///         match msg {
540///             PingPongMsg::Ping => {
541///                 println!("Ping! Count: {}", state);
542///                 *state -= 1;
543///                 if *state > 0 {
544///                     cast!(myself, PingPongMsg::Pong)?;
545///                 }
546///                 Ok(())
547///             }
548///             PingPongMsg::Pong => {
549///                 println!("Pong! Count: {}", state);
550///                 cast!(myself, PingPongMsg::Ping)?;
551///                 Ok(())
552///             }
553///         }
554///     });
555/// }
556/// ```
557///
558/// Reduces **~25 lines** of repetitive trait boilerplate to just the essential logic!
559///
560/// # Architecture Note
561///
562/// This macro generates a helper method `handle_msg()` rather than the trait method
563/// `handle()` directly because Rust proc macros running inside `impl MyActor` cannot
564/// modify the separate `impl Actor for MyActor` block generated by `#[actor]`.
565/// The trait method delegates to this helper method to bridge the gap.
566///
567/// # See Also
568///
569/// - [`#[actor]`](macro@actor) - Must be used first to set up the trait impl
570/// - [`actor_pre_start!`] - For defining initialization logic
571#[proc_macro]
572pub fn actor_handle(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
573    let body = parse_block_or_expr!(input);
574
575    #[cfg(feature = "async-trait")]
576    let tokens = quote::quote! {
577        pub async fn handle_msg(
578            &self,
579            myself: ractor::ActorRef<<Self as ractor::Actor>::Msg>,
580            msg: <Self as ractor::Actor>::Msg,
581            state: &mut <Self as ractor::Actor>::State,
582        ) -> ::core::result::Result<(), ractor::ActorProcessingErr> {
583            #body
584        }
585    };
586
587    #[cfg(not(feature = "async-trait"))]
588    let tokens = quote::quote! {
589        pub fn handle_msg(
590            &self,
591            myself: ractor::ActorRef<<Self as ractor::Actor>::Msg>,
592            msg: <Self as ractor::Actor>::Msg,
593            state: &mut <Self as ractor::Actor>::State,
594        ) -> impl ::core::future::Future<
595            Output=::core::result::Result<(), ractor::ActorProcessingErr>
596        > + Send {
597            async move {
598                #body
599            }
600        }
601    };
602
603    tokens.into()
604}