Getting Started

Let’s create and run our first actix application. We’ll create a new Cargo project that depends on actix and then run the application.

In previous section we already installed required rust version. Now let's create new cargo projects.

Ping actor

Let’s write our first actix application! Start by creating a new binary-based Cargo project and changing into the new directory:

cargo new actor-ping
cd actor-ping

Now, add actix as a dependency of your project by ensuring your Cargo.toml contains the following:

[dependencies]
actix = "0.10.0-alpha.3"
actix-rt = "1.1" # <-- Runtime for actix

Let's create an actor that will accept a Ping message and respond with the number of pings processed.

An actor is a type that implements the Actor trait:

extern crate actix;
use actix::prelude::*;

struct MyActor {
    count: usize,
}

impl Actor for MyActor {
    type Context = Context<Self>;
}

fn main() {}

Each actor has an execution context, for MyActor we are going to use Context<A>. More information on actor contexts is available in the next section.

Now we need to define the Message that the actor needs to accept. The message can be any type that implements the Message trait.

extern crate actix;
use actix::prelude::*;

#[derive(Message)]
#[rtype(result = "usize")]
struct Ping(usize);

fn main() {}

The main purpose of the Message trait is to define a result type. The Ping message defines usize, which indicates that any actor that can accept a Ping message needs to return usize value.

And finally, we need to declare that our actor MyActor can accept Ping and handle it. To do this, the actor needs to implement the Handler<Ping> trait.

extern crate actix;
use actix::prelude::*;

struct MyActor {
   count: usize,
}
impl Actor for MyActor {
    type Context = Context<Self>;
}

struct Ping(usize);

impl Message for Ping {
   type Result = usize;
}

impl Handler<Ping> for MyActor {
    type Result = usize;

    fn handle(&mut self, msg: Ping, _ctx: &mut Context<Self>) -> Self::Result {
        self.count += msg.0;

        self.count
    }
}

fn main() {}

That's it. Now we just need to start our actor and send a message to it. The start procedure depends on the actor's context implementation. In our case we can use Context<A> which is tokio/future based. We can start it with Actor::start() or Actor::create(). The first is used when the actor instance can be created immediately. The second method is used in case we need access to the context object before we can create the actor instance. In case of the MyActor actor we can use start().

All communication with actors goes through an address. You can do_send a message without waiting for a response, or send to an actor with a specific message. Both start() and create() return an address object.

In the following example we are going to create a MyActor actor and send one message.

Here we use the actix-rt as way to start our System and drive our main Future so we can easily .await for the messages sent to the Actor.

extern crate actix;
extern crate actix_rt;
use actix::prelude::*;
struct MyActor {
   count: usize,
}
impl Actor for MyActor {
    type Context = Context<Self>;
}

struct Ping(usize);

impl Message for Ping {
   type Result = usize;
}
impl Handler<Ping> for MyActor {
    type Result = usize;

    fn handle(&mut self, msg: Ping, ctx: &mut Context<Self>) -> Self::Result {
        self.count += msg.0;
        self.count
    }
}

#[actix_rt::main] 
async fn main() {
    // start new actor
    let addr = MyActor { count: 10 }.start();

    // send message and get future for result
    let res = addr.send(Ping(10)).await;

    // handle() returns tokio handle
    println!("RESULT: {}", res.unwrap() == 20);

    // stop system and exit
    System::current().stop();
}

#[actix_rt::main] starts the system and block until future resolves.

The Ping example is available in the examples directory.