Reference

API Overview

All the key types, traits, and macros in one place. For complete API documentation, see docs.rs/acton-reactive.

Core Types

ActonApp

The entry point. Its only job is to boot the system and hand you an ActorRuntime.

let mut runtime = ActonApp::launch_async().await;
MethodDescription
launch_async().awaitStart a new actor system (use this in async contexts)
launch()Start from a sync context. Panics if called inside a Tokio runtime.

ActorRuntime

What launch_async() returns. This is where actor creation, the broker, shutdown, and IPC live.

MethodDescription
new_actor::<T>()Create an actor builder (default name)
new_actor_with_name::<T>(name)Create a named actor builder
new_actor_with_config::<T>(config)Create a builder from an ActorConfig
spawn_actor::<T>(config, setup)Create, configure, and start in one call
spawn_actor_with_setup_fn::<T>(config, setup_fn)As above, with an async setup closure
broker()Access the message broker
actor_count()Number of top-level actors
shutdown_all().awaitGraceful shutdown of the whole system
ipc_registry()Access IPC type registry
ipc_expose(name, handle)Expose actor for IPC. Returns Result<(), IpcNameInUse>; it no longer replaces an existing registration
ipc_hide(name)Remove IPC exposure
ipc_lookup(name)Find an IPC-exposed actor
start_ipc_listener().awaitStart IPC listener (default config)
start_ipc_listener_with_config(cfg).awaitStart IPC listener with custom config

IPC methods require the ipc feature.

Actor Builder

ManagedActor<Idle, T> — configures an actor before spawning.

Handler-registration methods take &mut self and return &mut Self, while start() consumes the builder. So configure first, then start — you can't chain straight through into .start():

let mut builder = runtime.new_actor::<Counter>();
builder
    .mutate_on::<Increment>(handler)
    .act_on::<GetCount>(handler);

let handle = builder.start().await;
MethodDescription
mutate_on::<M>(handler)Register async state-changing handler
mutate_on_sync::<M>(handler)Register sync state-changing handler (no future allocation)
act_on::<M>(handler)Register async read-only handler
act_on_sync::<M>(handler)Register sync read-only handler (no future allocation)
try_mutate_on::<M, T, E>(handler)Async state-changing handler returning Result<T, E>
try_act_on::<M, T, E>(handler)Async read-only handler returning Result<T, E>
on_error::<M, E>(handler)Handle error E returned by a try_* handler for message M
before_start(hook)Lifecycle hook before the message loop starts
after_start(hook)Lifecycle hook after the message loop starts
before_stop(hook)Lifecycle hook before shutdown begins
after_stop(hook)Lifecycle hook after the message loop ends
create_child(name)Build a child actor under this one (Idle only — not available inside a handler)
expose_for_ipc()Expose actor for IPC using its ERN root name
start().awaitSpawn the actor, consuming the builder; returns its ActorHandle
handle()Get the handle before starting

ActorHandle

Reference to a running actor.

handle.send(Message).await;
handle.stop().await.ok();
MethodDescription
send(msg).awaitFire-and-forget message
ask(request).awaitSend a request and wait for the reply
ask_with_timeout(request, dur).awaitask with an explicit deadline
broadcast(msg).awaitPublish to the broker for all subscribers
stop().awaitStop the actor
subscribe::<M>().awaitSubscribe to broadcast messages
unsubscribe::<M>()Unsubscribe from broadcast messages (fire-and-forget)
unsubscribe_async::<M>().awaitUnsubscribe, awaiting delivery of the request to the broker
reply_address()Get this actor's address, for use as a return address
create_envelope(recipient)Create an envelope from this actor to recipient
supervise_with::<S>(&runtime, config, blueprint).awaitRegister a child with a blueprint; returns SupervisedChild. The framework restarts it
supervise(child).awaitAdopt an already-built actor for cascading shutdown. No blueprint, so never restarted
unsupervise(&ern).awaitRetire the record and stop the child, dropping its IPC names
release(&ern).awaitRetire the record and hand the child back still running
children()Children supervised through this actor's handles — every clone shares one map; handles go stale across a restart
find_child(&ern)Look up a direct child by ERN, with the same caveat
id()Get actor's identifier (Ern)
name()Get actor's root name

ask and Request

ask sends a request and waits for the reply. A message becomes askable by implementing Request, which names the reply through an associated type:

impl Request for GetCount {
    type Response = Count;
}

let count = handle.ask(GetCount).await?;

Because inboxes are FIFO, a completed ask also proves every message sent to that actor beforehand has been processed. Handlers are unchanged: they answer through the reply envelope, and an actor cannot tell an ask from a send.

ask always finishes, backed by a 30-second DEFAULT_ASK_TIMEOUT. Outcomes come back as AskError, which is #[non_exhaustive]:

VariantMeaning
NoReplyDelivered, but no answer is coming
TimedOut { after }The reply address is still live; the deadline expired
UndeliverableThe inbox was already closed
CancelledDelivery was abandoned during shutdown
UnexpectedReplyThe handler answered with a type the request does not declare
PeerRejected { code, detail }(IPC) The peer refused before dispatch; a retry is safe
TransportFailed { detail }(IPC) The connection failed; whether it was processed is unknown

Do not ask from inside a mutate_on handler. Mutable handlers are awaited inline on the message loop, so waiting for a reply stops the actor from processing the message that would produce it.

SupervisedChild

A reference to a supervised child that survives its restarts. ActorHandle names one incarnation and goes stale; SupervisedChild reads a status channel its supervisor publishes to.

MethodDescription
current()Handle for the incarnation running right now
wait_running().awaitBlock until an incarnation is up; errors once escalated
wait_generation(gen).awaitBlock until a specific incarnation is up
status()The published SupervisionStatus: state, generation, last reason

SupervisionState is Starting, Running, RestartPending, Restarting, Down (will not come back), or Escalated (allowance exhausted).

FlushBroadcasts

A barrier for broadcasts. broadcast completes when the broker has the message, not when subscribers do, and a broadcast carries no reply address for a subscriber to answer.

broker.ask(FlushBroadcasts).await?;   // answers BroadcastsFlushed

The reply cannot arrive until every earlier broadcast is sitting in every subscriber's inbox. That is delivery, not completion: to know a particular subscriber has handled it, ask that subscriber afterwards.

ActorRuntime::shutdown_all flushes the broker before signalling anything, so broadcasting and then shutting down is not a race.

Reply

Builds the future a handler must return.

MethodUsed withDescription
Reply::ready()mutate_on, act_onComplete immediately, no async work
Reply::pending(future)mutate_on, act_onWrap an async block
Reply::try_pending(future)try_mutate_on, try_act_onWrap an async block returning Result
Reply::try_ok(value)try_mutate_on, try_act_onImmediate success
Reply::try_err(error)try_mutate_on, try_act_onImmediate failure

Handler futures must be Send + Sync

Reply::pending produces a Pin<Box<dyn Future<Output = ()> + Send + Sync + 'static>>. The Sync bound catches people out: anything held across an .await inside the block must be Sync, not merely Send. For work whose future isn't Sync (many HTTP and DB clients), tokio::spawn it and message the result back to the actor instead. See Integration.

MessageContext

The second argument every handler receives — the message plus its routing information. Written ctx or envelope in examples.

MethodDescription
message()Reference to the message payload (an accessor, not a field)
reply_envelope()OutboundEnvelope addressed back to the sender
origin_envelope()OutboundEnvelope representing where the message came from
new_envelope(&address)OutboundEnvelope to a different recipient, keeping this actor as the return address

MessageContext is not exported from the prelude — you don't normally need to name it, since handlers are closures and the type is inferred.

OutboundEnvelope

A message prepared for sending. This is what reply_envelope() and create_envelope() hand you.

MethodDescription
send(msg).awaitSend a message via this envelope
try_send(msg).awaitSend, returning Err instead of waiting if the inbox is full
reply(msg)Synchronous send (discouraged — prefer send)
reply_to()The return address
recipient()The recipient address, if any

Macros

#[acton_actor]

Marks a struct as actor state.

#[acton_actor]
struct Counter {
    count: i32,
}

#[acton_message]

Marks a struct as a message.

#[acton_message]
struct Increment;

#[acton_message(ipc)]  // Enable IPC serialization
struct GetValue;

#[acton_main]

Sets up the async runtime.

#[acton_main]
async fn main() {
    let runtime = ActonApp::launch_async().await;
    // ...
}

Prelude

Import everything:

use acton_reactive::prelude::*;
CategoryItems
Macrosacton_actor, acton_message, acton_main
SystemActonApp, ActorRuntime, ActorConfig, Broker, BrokerRef, ParentRef
ActorsManagedActor, Idle, Started, ActorHandle
HandlersReply
Request/replyAskError, DEFAULT_ASK_TIMEOUT
MessagesBrokerRequest, BrokerRequestEnvelope, MessageAddress, OutboundEnvelope, ChildTerminated, SystemSignal, FlushBroadcasts, BroadcastsFlushed
SupervisionSupervisedChild, SupervisionStatus, SupervisionState, SupervisionError, Escalation, RestartPolicy, TerminationReason, SupervisionStrategy, SupervisionDecision, RestartLimiter, RestartLimiterConfig, RestartLimitExceeded, RestartStats, RestartGeneration, ChildIndex, BackoffDelay, MAX_SUPERVISION_DEPTH
Supervision eventsChildSupervised, ChildRestarted, SupervisionEscalated
TraitsActonMessage, ActorHandleInterface, Broadcaster, Request, Subscribable, Subscriber
Re-exportseverything from acton_ern, plus async_trait and tokio
IPC (ipc feature)IpcClient, IpcClientConfig, IpcConfig, IpcEnvelope, IpcError, IpcResponse, IpcListenerHandle, IpcListenerStats, IpcTypeRegistry, RemoteActorRef, ShutdownResult, and the RemoteRequest trait

Note that MessageContext is deliberately not in the prelude; handlers receive it as an inferred closure parameter.

Previous
Integration