Getting started
Build concurrent Rust apps without locks, race conditions, or shared state nightmares.
Acton gives you independent workers that own their state privately and communicate through messages. If you can write async Rust, you can write actors—and your concurrent code will be correct by construction.
Quick Start
Get running in 5 minutes.
Core Concepts
Understand actors, messages, and handlers.
Building Apps
Practical patterns for real applications.
API Reference
Quick reference for types and traits.
Choose your path
New to concurrent programming?
Start with What are Actors? to understand the concepts, then work through the Quick Start.
Experienced with actors?
Jump straight to Installation and Your First Actor. Check the Cheatsheet for quick patterns.
Actors eliminate data races by making isolation the default
Traditional concurrent Rust requires careful lock management:
// Shared state protected by locks...
let counter = Arc::new(Mutex::new(0));
let counter_clone = counter.clone();
// Spawn threads...
let handle = thread::spawn(move || {
let mut num = counter_clone.lock().unwrap(); // Hope this doesn't deadlock!
*num += 1;
});
// Hope you got all the Arc<Mutex<...>> right...
The problems: Lock contention, deadlocks, shared state bugs, and subtle race conditions that only appear in production.
Acton's solution: Each actor owns its data privately. No locks needed.
#[acton_actor]
struct Counter { count: i32 }
#[acton_message]
struct Increment;
builder.mutate_on::<Increment>(|actor, _msg| {
actor.model.count += 1;
Reply::ready()
});
handle.send(Increment).await;
What you get: No locks, isolated failures, guaranteed message ordering, and compile-time safety. Data races become impossible because actors never share mutable state.
A complete example in 20 lines
use acton_reactive::prelude::*;
#[acton_actor]
struct Counter { count: i32 }
#[acton_message]
struct Increment;
#[acton_main]
async fn main() {
let mut app = ActonApp::launch();
let handle = app
.new_actor::<Counter>()
.mutate_on::<Increment>(|actor, _| {
actor.model.count += 1;
println!("Count: {}", actor.model.count);
Reply::ready()
})
.start()
.await;
handle.send(Increment).await.ok();
handle.send(Increment).await.ok();
handle.send(Increment).await.ok();
app.shutdown_all().await.ok();
}
Output:
Count: 1
Count: 2
Count: 3
Each message is processed in order. The actor's state is never accessed concurrently. Ready to build? Start with Installation.
Two handler types cover every use case
| If you need to... | Use this | Why |
|---|---|---|
| Modify the actor's data | mutate_on | Exclusive access, one message at a time |
| Read the actor's data | act_on | Multiple reads can run concurrently |
// For mutations - one at a time, exclusive access
builder.mutate_on::<Increment>(|actor, _| {
actor.model.count += 1;
Reply::ready()
});
// For queries - can run concurrently with other reads
builder.act_on::<GetCount>(|actor, _| {
Reply::with(actor.model.count)
});
When in doubt, use mutate_on
It's safer because it processes one message at a time. Optimize to act_on when you're sure the handler only reads.
See Messages & Handlers for the complete picture.
Current status
Pre-1.0 Software
acton-reactive is under active development. The API is stabilizing but may change before 1.0. Breaking changes bump the minor version (e.g., 0.7 → 0.8).
Start building now
- Installation — Add acton-reactive to your project
- Your First Actor — Hands-on tutorial with working code
- What are Actors? — Understand the actor model
- Cheatsheet — Copy-paste patterns for common tasks

