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}