# 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:

| Variant | Description | Retryable? |
|---------|-------------|------------|
| `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.
