|
| 1 | +# `ActorHandle<A>` |
| 2 | + |
| 3 | +An `ActorHandle<A>` is a reference to a **local, running actor** of type `A`. It provides access to the actor's messaging ports, lifecycle status, and control methods (such as stop signals). |
| 4 | + |
| 5 | +Unlike remote references (e.g. `ActorRef<A>`), which may refer to actors on other `Proc`s, an `ActorHandle` only exists within the same `Proc` and can be sent messages without requiring serialization. |
| 6 | + |
| 7 | +## Definition |
| 8 | + |
| 9 | +```rust |
| 10 | +pub struct ActorHandle<A: Actor> { |
| 11 | + cell: InstanceCell, |
| 12 | + ports: Arc<Ports<A>>, |
| 13 | +} |
| 14 | +``` |
| 15 | +An `ActorHandle` contains: |
| 16 | +- `cell` is the actor’s internal runtime state, including identity and lifecycle metadata. |
| 17 | +- `ports` is a shared dictionary of all typed message ports available to the actor. |
| 18 | + |
| 19 | +This handle is cloneable, sendable across tasks, and allows interaction with the actor via messaging, status observation, and controlled shutdown. |
| 20 | + |
| 21 | +## Methods |
| 22 | + |
| 23 | +### `new` (internal) |
| 24 | + |
| 25 | +Constructs a new `ActorHandle` from its backing `InstanceCell` and `Ports`. This is called by the runtime when spawning a new actor. |
| 26 | +```rust |
| 27 | +pub(crate) fn new(cell: InstanceCell, ports: Arc<Ports<A>>) -> Self { |
| 28 | + Self { cell, ports } |
| 29 | +} |
| 30 | +``` |
| 31 | + |
| 32 | +### `cell` (internal) |
| 33 | + |
| 34 | +Returns the underlying `InstanceCell` backing the actor. |
| 35 | +```rust |
| 36 | +pub(crate) fn cell(&self) -> &InstanceCell { |
| 37 | + &self.cell |
| 38 | +} |
| 39 | +``` |
| 40 | + |
| 41 | +### `actor_id` |
| 42 | + |
| 43 | +Returns the `ActorId` of the actor represented by this handle. |
| 44 | +```rust |
| 45 | +pub fn actor_id(&self) -> &ActorId { |
| 46 | + self.cell.actor_id() |
| 47 | +} |
| 48 | +``` |
| 49 | + |
| 50 | +### `drain_and_stop` |
| 51 | + |
| 52 | +Signals the actor to drain any pending messages and then stop. This enables a graceful shutdown procedure. |
| 53 | +```rust |
| 54 | +pub fn drain_and_stop(&self) -> Result<(), ActorError> { |
| 55 | + self.cell.signal(Signal::DrainAndStop) |
| 56 | +} |
| 57 | +``` |
| 58 | + |
| 59 | +### `status` |
| 60 | + |
| 61 | +Returns a watch channel that can be used to observe the actor's lifecycle status (e.g., running, stopped, crashed). |
| 62 | +```rust |
| 63 | +pub fn status(&self) -> watch::Receiver<ActorStatus> { |
| 64 | + self.cell.status().clone() |
| 65 | +} |
| 66 | +``` |
| 67 | + |
| 68 | +### `send` |
| 69 | + |
| 70 | +Sends a message of type `M` to the actor. The actor must implement `Handler<M>` for this to compile. |
| 71 | + |
| 72 | +Messages sent via an `ActorHandle` are always delivered in-process and do not require serialization. |
| 73 | +```rust |
| 74 | +pub fn send<M: Message>(&self, message: M) -> Result<(), MailboxSenderError> |
| 75 | +where |
| 76 | + A: Handler<M>, |
| 77 | +{ |
| 78 | + self.ports.get().send(message) |
| 79 | +} |
| 80 | +``` |
| 81 | + |
| 82 | +### `port` |
| 83 | + |
| 84 | +Returns a reusable port handle for the given message type. |
| 85 | +```rust |
| 86 | +pub fn port<M: Message>(&self) -> PortHandle<M> |
| 87 | +where |
| 88 | + A: Handler<M>, |
| 89 | +{ |
| 90 | + self.ports.get() |
| 91 | +} |
| 92 | +``` |
| 93 | + |
| 94 | +### `bind` |
| 95 | + |
| 96 | +Creates a remote reference (`ActorRef<R>`) by applying a `Binds<A>` implementation. |
| 97 | +```rust |
| 98 | +pub fn bind<R: Binds<A>>(&self) -> ActorRef<R> { |
| 99 | + self.cell.bind(self.ports.as_ref()) |
| 100 | +} |
| 101 | +``` |
| 102 | + |
| 103 | +### Binding and ActorRefs |
| 104 | + |
| 105 | +The `bind()` method on `ActorHandle` creates an `ActorRef<R>` for a given remote-facing reference type `R`. This is the bridge between a local actor instance and its externally visible interface. |
| 106 | +```rust |
| 107 | +pub fn bind<R: Binds<A>>(&self) -> ActorRef<R> |
| 108 | +``` |
| 109 | +This method requires that `R` implements the `Binds<A>` trait. The `Binds` trait specifies how to associate a remote-facing reference type with the concrete ports handled by the actor: |
| 110 | +```rust |
| 111 | +pub trait Binds<A: Actor>: RemoteActor { |
| 112 | + fn bind(ports: &Ports<A>); |
| 113 | +} |
| 114 | +``` |
| 115 | +In practice, `A` and `R` are usually the same type; this is the pattern produced by the `#[export]` macro. But `R` can also be a trait object or wrapper that abstracts over multiple implementations. |
| 116 | + |
| 117 | +### Binding internals |
| 118 | + |
| 119 | +Calling `bind()` on the `ActorHandle`: |
| 120 | +1. Invokes the `Binds<A>::bind()` implementation for `R`, registering the actor's message handlers into the `Ports<A>` dictionary. |
| 121 | +2. Always binds the `Signal` type (used for draining, stopping, and supervision). |
| 122 | +3. Records the bound message types into `InstanceState::exported_named_ports`, enabling routing and diagnostics. |
| 123 | +4. Constructs the final `ActorRef<R>` using `ActorRef::attest(...)`, which assumes the type-level correspondence between `R` and the bound ports. |
| 124 | + |
| 125 | +The result is a typed, routable reference that can be shared across `Proc`s. |
| 126 | + |
| 127 | +## `IntoFuture for ActorHandle` |
| 128 | + |
| 129 | +### Overview |
| 130 | + |
| 131 | +An `ActorHandle<A>` can be awaited directly thanks to its `IntoFuture` implementation. Awaiting the `handle` waits for the actor to shut down. |
| 132 | + |
| 133 | +### Purpose |
| 134 | + |
| 135 | +This allows you to write: |
| 136 | +```rust |
| 137 | +let status = actor_handle.await; |
| 138 | +``` |
| 139 | +Instead of: |
| 140 | +```rust |
| 141 | +let mut status = actor_handle.status(); |
| 142 | +status.wait_for(ActorStatus::is_terminal).await; |
| 143 | +``` |
| 144 | + |
| 145 | +### Behavior |
| 146 | + |
| 147 | +When awaited, the handle: |
| 148 | +- Subscribes to the actor’s status channel, |
| 149 | +- Waits for a terminal status (`Stopped`, `Crashed`, etc.), |
| 150 | +- Returns the final status, |
| 151 | +- Returns `ActorStatus::Unknown` if the channel closes unexpectedly. |
| 152 | + |
| 153 | +### Implementation |
| 154 | +```rust |
| 155 | +impl<A: Actor> IntoFuture for ActorHandle<A> { |
| 156 | + type Output = ActorStatus; |
| 157 | + type IntoFuture = BoxFuture<'static, Self::Output>; |
| 158 | + |
| 159 | + fn into_future(self) -> Self::IntoFuture { |
| 160 | + let future = async move { |
| 161 | + let mut status_receiver = self.cell.status().clone(); |
| 162 | + let result = status_receiver.wait_for(ActorStatus::is_terminal).await; |
| 163 | + match result { |
| 164 | + Err(_) => ActorStatus::Unknown, |
| 165 | + Ok(status) => status.passthrough(), |
| 166 | + } |
| 167 | + }; |
| 168 | + future.boxed() |
| 169 | + } |
| 170 | +} |
| 171 | +``` |
| 172 | +### Summary |
| 173 | + |
| 174 | +This feature is primarily ergonomic. It provides a natural way to synchronize with the termination of an actor by simply awaiting its handle. |
0 commit comments