#[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 receivestate: The state type your actor will maintainargs: 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 trait methods delegate to helper methods on your type.
- You provide those helpers via the
actor_pre_start!andactor_handle!macros.
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 theasync_traitcrate for trait methods. When disabled, usesimpl Futurereturn type position impl trait (RPIT).
§See Also
actor_pre_start!- Macro for defining the initialization logicactor_handle!- Macro for defining message handling logic