# Error Handling
Robust error handling is essential in any concurrent system. rsActor provides comprehensive mechanisms to deal with errors at every level.
## Error Categories
Errors in rsActor fall into these categories:
1. **Actor Lifecycle Errors**: Errors in `on_start`, `on_run`, or `on_stop` (your custom `Actor::Error` type)
2. **Communication Errors**: Errors during message sending (`rsactor::Error`)
3. **Message Handling Errors**: Errors within handler methods (your custom reply types)
4. **Panics**: If an actor task panics (caught by JoinHandle)
## rsactor::Error
The framework's built-in error type for communication failures:
| `Error::Send` | Actor's mailbox is closed (actor stopped) | No |
| `Error::Receive` | Reply channel was dropped before response | No |
| `Error::Timeout` | Operation exceeded the specified timeout | Yes |
| `Error::Downcast` | Reply type downcast failed (programming error) | No |
| `Error::Runtime` | Actor lifecycle runtime failure | No |
| `Error::MailboxCapacity` | Mailbox capacity configuration error | No |
| `Error::Join` | Spawned task panicked or was cancelled | No |
### Error Utilities
```rust
match actor_ref.tell(msg).await {
Ok(()) => { /* delivered */ }
Err(e) => {
// Check if retrying might help
if e.is_retryable() {
// Only Timeout errors are retryable
}
// Get actionable debugging suggestions
for tip in e.debugging_tips() {
println!(" - {}", tip);
}
}
}
```
## Custom Actor Errors
Define your own error type for lifecycle methods. Any type implementing `Send + Debug + 'static` works:
```rust
#[derive(Debug, thiserror::Error)]
enum MyActorError {
#[error("Database error: {0}")]
DbError(#[from] sqlx::Error),
#[error("Network timeout")]
NetworkTimeout,
}
impl Actor for MyActor {
type Error = MyActorError;
// ...
}
```
## on_tell_result Hook
For fire-and-forget (`tell`) messages, errors in the handler are silently dropped by default. The `on_tell_result` hook provides observability:
```rust
#[message_handlers]
impl MyActor {
#[handler]
async fn handle_command(&mut self, msg: Command, _: &ActorRef<Self>) {
self.execute(msg)?;
}
}
// In the Message<Command> trait implementation (auto-generated by #[handler]):
// on_tell_result is called with the handler's return value when sent via tell()
```
When implementing `Message<T>` manually, override `on_tell_result`:
```rust
impl Message<Command> for MyActor {
type Reply = Result<(), CommandError>;
async fn handle(&mut self, msg: Command, _: &ActorRef<Self>) -> Self::Reply {
self.execute(msg)
}
fn on_tell_result(result: &Self::Reply, _actor_ref: &ActorRef<Self>) {
if let Err(e) = result {
tracing::error!("Command failed (fire-and-forget): {e:?}");
}
}
}
```
Use `#[handler(no_log)]` to suppress the default warning log for tell errors.
## Dead Letters
When a message cannot be delivered, rsActor automatically records a dead letter with structured tracing. See [Dead Letter Tracking](advanced/dead_letters.md) for details.