Getting Started¶
Welcome! This guide will get you up and running with AirsSys-RT in under 20 minutes. You'll create your first actor, send messages, and understand the core workflow.
What You'll Build¶
A simple counter actor that: - Receives increment/decrement messages - Maintains internal state - Responds to queries - Handles shutdown gracefully
Prerequisites:
- Rust 1.70 or higher
- Basic understanding of async/await in Rust
- Familiarity with Cargo
Estimated time: 15-20 minutes
Step 1: Add AirsSys-RT to Your Project¶
Create a new Rust project and add the dependency:
Add to your Cargo.toml:
[dependencies]
airssys-rt = "0.1"
tokio = { version = "1.47", features = ["full"] }
async-trait = "0.1"
serde = { version = "1.0", features = ["derive"] }
Why these dependencies?
- airssys-rt - The actor runtime framework
- tokio - Async runtime for concurrent operations
- async-trait - Required for async trait methods
- serde - Message serialization (optional but recommended)
Step 2: Define Your Messages¶
Messages are the data your actor receives. Create clear message types using enums:
use airssys_rt::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
enum CounterMessage {
Increment,
Decrement,
GetValue,
Shutdown,
}
impl Message for CounterMessage {
const MESSAGE_TYPE: &'static str = "counter";
}
Key concepts:
- Enums for clarity: Each variant represents a distinct operation
- Derive Clone: Messages are cloned when sent
- Derive Serialize: Enables message routing and persistence
- MESSAGE_TYPE constant: Identifies message type in the system
Step 3: Implement Your Actor¶
Actors encapsulate state and behavior. Here's a simple counter:
use async_trait::async_trait;
use std::fmt;
struct CounterActor {
value: i32,
}
// Define error type
#[derive(Debug)]
struct CounterError(String);
impl fmt::Display for CounterError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Counter error: {}", self.0)
}
}
impl std::error::Error for CounterError {}
#[async_trait]
impl Actor for CounterActor {
type Message = CounterMessage;
type Error = CounterError;
async fn handle_message<B: MessageBroker<Self::Message>>(
&mut self,
message: Self::Message,
context: &mut ActorContext<Self::Message, B>,
) -> Result<(), Self::Error> {
match message {
CounterMessage::Increment => {
self.value += 1;
println!("Counter incremented to: {}", self.value);
}
CounterMessage::Decrement => {
self.value -= 1;
println!("Counter decremented to: {}", self.value);
}
CounterMessage::GetValue => {
println!("Current value: {}", self.value);
}
CounterMessage::Shutdown => {
println!("Shutting down counter actor");
return Err(CounterError("Shutdown requested".to_string()));
}
}
// Record that we processed a message
context.record_message();
Ok(())
}
}
Understanding the Actor trait:
- Associated types: Define your message and error types
- handle_message: Core message processing logic
- context: Provides actor metadata and messaging capabilities
- Error handling: Returning
Errsignals the supervisor
Step 4: Create and Run Your Actor¶
Now bring it all together in your main.rs:
use airssys_rt::prelude::*;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::fmt;
// ... (include message and actor definitions from above)
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Starting actor...\n");
// Create actor instance
let mut actor = CounterActor { value: 0 };
// Create actor context with address and message broker
let address = ActorAddress::named("counter");
let broker = InMemoryMessageBroker::<CounterMessage>::new();
let mut context = ActorContext::new(address, broker);
// Create lifecycle tracker
let mut lifecycle = ActorLifecycle::new();
// Start the actor
actor.pre_start(&mut context).await?;
lifecycle.transition_to(ActorState::Running);
// Process messages
let messages = vec![
CounterMessage::Increment,
CounterMessage::Increment,
CounterMessage::GetValue,
CounterMessage::Decrement,
CounterMessage::GetValue,
];
for msg in messages {
match actor.handle_message(msg, &mut context).await {
Ok(()) => println!("✓ Message processed"),
Err(e) => {
println!("✗ Error: {e}");
let action = actor.on_error(e, &mut context).await;
if action == ErrorAction::Stop {
lifecycle.transition_to(ActorState::Stopping);
break;
}
}
}
}
// Graceful shutdown
lifecycle.transition_to(ActorState::Stopping);
actor.post_stop(&mut context).await?;
lifecycle.transition_to(ActorState::Stopped);
println!("\nActor lifecycle complete!");
Ok(())
}
Step 5: Run Your Application¶
Expected output:
=== Getting Started Example ===
1. Starting actor...
Actor is running
2. Sending messages...
Counter incremented to: 1
✓ Message processed (total: 1)
Counter incremented to: 2
✓ Message processed (total: 2)
Current value: 2
✓ Message processed (total: 3)
Counter decremented to: 1
✓ Message processed (total: 4)
Current value: 1
✓ Message processed (total: 5)
3. Shutting down...
Shutting down counter actor
4. Final state:
State: Stopped
Messages processed: 5
Restart count: 0
=== Example Complete ===
Understanding the Workflow¶
Let's break down what just happened:
1. Actor Creation¶
- Creates actor instance with initial state - Actor owns its data (no shared state) - Mutable reference allows state changes2. Context Setup¶
let address = ActorAddress::named("counter");
let broker = InMemoryMessageBroker::<CounterMessage>::new();
let mut context = ActorContext::new(address, broker);
3. Lifecycle Management¶
let mut lifecycle = ActorLifecycle::new();
actor.pre_start(&mut context).await?;
lifecycle.transition_to(ActorState::Running);
Created → Starting → Running
- Lifecycle tracking for supervision
4. Message Processing¶
- Messages processed synchronously (one at a time) - No race conditions - actor has exclusive access to its state - Performance: ~31.5ns per message processing5. Error Handling¶
- Actor decides supervision action - Resume: Continue running - Stop: Shut down gracefully - Restart: Reset state and continue - Escalate: Let supervisor decide6. Graceful Shutdown¶
lifecycle.transition_to(ActorState::Stopping);
actor.post_stop(&mut context).await?;
lifecycle.transition_to(ActorState::Stopped);
Running → Stopping → Stopped
- Ensures no resource leaks
Next Steps¶
Congratulations! You've created your first actor. Here's what to explore next:
🎯 Learn More Patterns¶
- Actor Development Tutorial - Deep dive into actor patterns
- Message Passing Guide - Advanced messaging patterns
🔧 Add Fault Tolerance¶
- Supervisor Patterns - Build resilient systems
- Error Handling - Robust error strategies
📊 Monitor Your System¶
- Monitoring Guide - Observability and health checks
âš¡ Optimize Performance¶
- Performance Guide - Benchmarking and tuning
- BENCHMARKING.md - Baseline performance metrics
Troubleshooting¶
Problem: "trait Message is not implemented"¶
Solution: Make sure you've implemented the Message trait:
Problem: "future cannot be sent between threads safely"¶
Solution: Ensure your message types are Send + Sync:
Problem: Messages not processing¶
Solution: Add a small delay after sending to allow processing:
Or use request/reply pattern for synchronous behavior (see Message Passing Guide).
Problem: Actor panics on error¶
Solution: Return Err(YourError) instead of panicking. The supervisor will handle it:
Quick Reference¶
Import Everything You Need¶
Minimal Actor Template¶
#[derive(Debug, Clone, Serialize, Deserialize)]
enum MyMessage { /* variants */ }
impl Message for MyMessage {
const MESSAGE_TYPE: &'static str = "my_message";
}
struct MyActor { /* fields */ }
#[async_trait]
impl Actor for MyActor {
type Message = MyMessage;
type Error = MyError;
async fn handle_message<B: MessageBroker<Self::Message>>(
&mut self,
message: Self::Message,
context: &mut ActorContext<Self::Message, B>,
) -> Result<(), Self::Error> {
// Handle message
context.record_message();
Ok(())
}
}
What You Learned¶
✅ How to add AirsSys-RT to your project
✅ Defining message types with the Message trait
✅ Implementing the Actor trait
✅ Creating an actor system
✅ Spawning actors and sending messages
✅ Understanding the basic message processing workflow
Ready for more? Check out the Actor Development Tutorial to learn advanced patterns and best practices!