Actix is a Rust actor framework.
Any
type).To use actix
, add this to your Cargo.toml
:
toml
[dependencies]
actix = "0.4"
In order to use actix you first need to create a System
.
```rust,ignore extern crate actix;
fn main() { let system = actix::System::new("test");
system.run();
} ```
Actix uses the tokio event loop.
System::new()
creates a new event loop and starts the System
actor.
system.run()
starts the tokio event loop, and will finish once the System
actor
receives the SystemExit
message.
Let's create a simple Actor.
In order to define an actor you need to define a struct and have it implement
the Actor
trait.
```rust extern crate actix; use actix::{msgs, Actor, Addr, Arbiter, Context, Syn, System};
struct MyActor;
impl Actor for MyActor {
type Context = Context
fn started(&mut self, ctx: &mut Self::Context) {
println!("I am alive!");
Arbiter::system().do_send(msgs::SystemExit(0));
}
}
fn main() { let system = System::new("test");
let addr: Addr<Syn, _> = MyActor.start();
system.run();
} ```
Spawning a new actor is achieved via the start
and create
methods of
the Actor
trait. It provides several different ways of creating actors, for details check docs.
You can implement started
, stopping
and stopped
methods of the Actor trait.
started
gets called when actor starts and stopping
when actor finishes.
Check API documentation
for more information on the actor lifecycle.
An Actor communicates with another Actor by sending messages. In actix all messages are typed.
Let's define a simple Sum
message with two usize
parameters, and an actor which will
accept this message and return the sum of those two numbers.
```rust extern crate actix; extern crate futures; use futures::{future, Future}; use actix::*;
// this is our Message struct Sum(usize, usize);
// we have to define the response type for Sum
message
impl Message for Sum {
type Result = usize;
}
// Actor definition struct Summator;
impl Actor for Summator {
type Context = Context
// now we need to define MessageHandler
for the Sum
message.
impl Handler
fn handle(&mut self, msg: Sum, ctx: &mut Context<Self>) -> Self::Result {
msg.0 + msg.1
}
}
fn main() { let system = System::new("test");
let addr: Addr<Unsync, _> = Summator.start();
let res = addr.send(Sum(10, 5)); // <- send message and get future for result
system.handle().spawn(res.then(|res| {
match res {
Ok(result) => println!("SUM: {}", result),
_ => println!("Something wrong"),
}
Arbiter::system().do_send(msgs::SystemExit(0));
future::result(Ok(()))
}));
system.run();
} ```
All communications with actors go through an Addr
object. You can send
a message
without waiting for a response, or call
an actor with specific message. The Message
trait defines the result type for a message. There are different types of addresses.
Unsync
is an address
of an actor that runs in the same arbiter (event loop). If an actor is running in a different
thread, Syn
has to be used.
You may have noticed that methods of Actor
and Handler
traits accept &mut self
, so you are
welcome to store anything in an actor and mutate it whenever necessary.
Address objects require an actor type, but if we just want to send a specific message to
an actor that can handle the message, we can use the Subscriber
interface. Let's create
a new actor that uses Subscriber
. This example will also show how to use standard future objects.
We will use Rust's unstable proc_macro
feature for message and handler definitions.
```rust,ignore
extern crate actix; use std::time::Duration; use actix::*;
struct Ping { pub id: usize }
// Actor definition
struct Game {
counter: usize,
addr: Recipient
impl Game {
#[simple(Ping)]
// simple message handler for Ping message
fn ping(&mut self, id: usize, ctx: &mut Context<Self>) {
self.counter += 1;
if self.counter > 10 {
Arbiter::system().do_send(msgs::SystemExit(0));
} else {
println!("Ping received {:?}", id);
// wait 100 nanos
ctx.run_later(Duration::new(0, 100), move |act, _| {
act.addr.do_send(Ping{id: id + 1});
});
}
}
}
fn main() { let system = System::new("test");
// To get a Subscriber object, we need to use a different builder method
// which will allow postponing actor creation
let _: Addr<Unsync, _> = Game::create(|ctx| {
// now we can get an address of the first actor and create the second actor
let addr: Addr<Unsync, _> = ctx.address();
let addr2: Addr<Unsync, _> = Game{counter: 0, addr: addr.recipient()}.start();
// let's start pings
addr2.do_send(Ping{id: 10});
// now we can finally create first actor
Game{counter: 0, addr: addr2.recipient()}
});
system.run();
} ```
More information on signal handling is in the signal module.
There is a chat example which provides a basic example of networking client/server service.
You may consider checking out fectl utility. It is written
with actix
and shows how to create networking application with relatively complex interactions.
All contributions are welcome, if you have a feature request don't hesitate to open an issue!
This project is licensed under either of
at your option.
Contribution to the actix-web crate is organized under the terms of the Contributor Covenant, the maintainer of actix, @fafhrd91, promises to intervene to uphold that code of conduct.