actor

Attribute Macro actor 

Source
#[actor]
Expand description

Attribute macro to define an Actor.

This macro dramatically reduces boilerplate when implementing the ractor::Actor trait. It automatically generates the trait implementation with proper type definitions and method signatures, delegating to user-defined helper methods.

§Arguments

The macro accepts three named arguments:

  • msg: The message type your actor will receive
  • state: The state type your actor will maintain
  • args: The arguments type used to initialize your actor (defaults to ())

§Example

use dsl_ractor::actor;
use ractor::Actor;

#[derive(Debug, Clone)]
enum CounterMsg {
    Increment,
    Print,
}

#[actor(msg = CounterMsg, state = i32, args = i32)]
struct CounterActor;

§What This Generates (and why helpers exist)

The macro expands into a complete Actor trait implementation:

The helpers are required because a proc macro attached to a struct cannot also inject items into a separate impl Actor for ... block. Delegation keeps the API ergonomic while satisfying Rust’s macro hygiene rules.

// Your original struct definition
struct CounterActor;

// Generated code (simplified):
#[async_trait]  // only with "async-trait" feature
impl Actor for CounterActor {
    type Msg = CounterMsg;
    type State = i32;
    type Arguments = i32;

    async fn pre_start(
        &self,
        myself: ActorRef<Self::Msg>,
        args: Self::Arguments
    ) -> Result<Self::State, ActorProcessingErr> {
        self.on_start(myself, args).await
    }

    async fn handle(
        &self,
        myself: ActorRef<Self::Msg>,
        msg: Self::Msg,
        state: &mut Self::State
    ) -> Result<(), ActorProcessingErr> {
        self.handle_msg(myself, msg, state).await
    }
}

§Comparison: Before vs After

Without this macro (raw Ractor):

use ractor::{Actor, ActorProcessingErr, ActorRef, async_trait};

#[derive(Debug, Clone)]
enum CounterMsg {
    Increment,
    Print,
}

struct CounterActor;

#[async_trait]
impl Actor for CounterActor {
    type Msg = CounterMsg;
    type State = i32;
    type Arguments = i32;

    async fn pre_start(
        &self,
        _myself: ActorRef<Self::Msg>,
        args: Self::Arguments,
    ) -> Result<Self::State, ActorProcessingErr> {
        Ok(args)  // 30+ lines of boilerplate just to get here!
    }

    async fn handle(
        &self,
        _myself: ActorRef<Self::Msg>,
        message: Self::Msg,
        state: &mut Self::State,
    ) -> Result<(), ActorProcessingErr> {
        match message {
            CounterMsg::Increment => {
                *state += 1;
                Ok(())
            }
            CounterMsg::Print => {
                println!("Count: {}", state);
                Ok(())
            }
        }
    }
}

With this macro:

use dsl_ractor::{actor, actor_pre_start, actor_handle};

#[derive(Debug, Clone)]
enum CounterMsg {
    Increment,
    Print,
}

#[actor(msg = CounterMsg, state = i32, args = i32)]
struct CounterActor;

impl CounterActor {
    actor_pre_start!(Ok(args));

    actor_handle!({
        match msg {
            CounterMsg::Increment => {
                *state += 1;
                Ok(())
            }
            CounterMsg::Print => {
                println!("Count: {}", state);
                Ok(())
            }
        }
    });
}

Notice how the macro:

  • Eliminates the need to write impl Actor for ...
  • No need to manually define associated types
  • No need to write method signatures with complex return types
  • Just focus on your actor’s logic!

§Feature Flags

  • async-trait: When enabled, uses the async_trait crate for trait methods. When disabled, uses impl Future return type position impl trait (RPIT).

§See Also