anchorRust
What is Rust?
Aside from being "an iron oxide" according to Wikipedia, Rust is also the name of a programming language that was originally invented by Graydon Hoare back in 2006. You can think about it as an interesting mix of high-level languages like JavaScript or Ruby and low-level high-performance languages like C++.
Similar to C++ it is a compiled language without a garbage collector. The fact that it usually uses LLVM to compile to machine code means that a lot of the optimizations that were developed to compile C/C++ code can also be applied to Rust programs.
Similar to JavaScript or Python the language often feels more high-level than C/C++, and it has a built-in dependency manager called cargo
. This is somewhat similar to npm
in JavaScript or bundler
in Ruby, with the main difference that cargo
is also used to build and test your applications and libraries.
The main advantage over all the other languages is safety. The Rust compiler is often quite strict on what data you are allowed to access at what point in the application logic, because it knows about concepts like threads and potential race conditions. This might not seem relevant to JavaScript developers, but even there with nested callbacks you might easily run into a situation where the thing you're trying to access has been deleted already. This issue is impossible with Rust.
Why would you use it? It is very fast, it can be embedded into scripting languages if raw speed is needed, it can compile to WebAssembly, and most of all, it is much safer than C++, which would also be a candidate for all the previous points.
How to get started? Follow the instructions at rustup.rs
anchorActors
The "actor model" is the main primitive that powers the Erlang programming language and its descendant, [Elixir]. It describes a programming model that simplifies the development of concurrent and multi-threaded applications or even applications that run distributed on multiple machines.
An actor is a thing that can only be interacted with using "messages". A message can basically be anything that the actor can understand and in response to a message an actor is allowed to do several things, including:
- send a response
- send messages to other actors
- change its own state
Let's look at a simplified example in JavaScript syntax:
class CounterActor {
constructor() {
this.count = 0;
}
onReceive(message) {
if (message.type === 'plus-one') {
this.count += 1;
}
return this.count;
}
}
The CounterActor
class in this example is initialized with an internal state called count
that is set to zero and it responds to plus-one
messages by increasing the count
state and returning the new value.
The complexity of actors is relatively low, and that is because the complexity is usually hidden in the actor frameworks that are used to run these types of primitives in the end. One example of such an actor framework is [actix], which we will have a closer look at now.
anchoractix
[actix] is the low-level actor framework that powers actix-web, a high-performance web framework for the [Rust][rust] programming language.
While actix-web is interesting and worth another blog post, we will focus on the low-level primitive [actix][actix] for now as it is vital to understanding the higher level concepts.
To get started with actix, let's port our CounterActor
above to Rust:
use actix::prelude::*;
// `PlusOne` message implementation
struct PlusOne;
impl Message for PlusOne {
type Result = u32;
}
// `CounterActor` implementation
struct CounterActor {
count: u32,
}
impl CounterActor {
pub fn new() -> CounterActor {
CounterActor { count: 0 }
}
}
impl Actor for CounterActor {
type Context = Context<Self>;
}
impl Handler<PlusOne> for CounterActor {
type Result = u32;
fn handle(&mut self, _msg: PlusOne, _ctx: &mut Context<Self>) -> u32 {
self.count += 1;
self.count
}
}
Since Rust is a typed language all structures need to be declared upfront. In the snippet above we first import all the necessary things from the actix::prelude
module, and then we define how a PlusOne
message should look like. In the JavaScript implementation the message
had a type
property, but since we have a strict type system available in Rust there is no need to explicitly declare that. That leaves us with an empty PlusOne
message, indicated by the struct PlusOne
which does not have any content. The message does have a Result
type though, defined by type Result = u32;
which means "unsigned 32 bit integer".
The CounterActor
implementation is another struct
which is roughly similar to a class
in JavaScript. It does implement several "traits", which is very roughly what are called "interfaces" in e.g. TypeScript or Java.
The Actor
trait defines that CounterActor
is in fact an actor that complies with the necessary interface to be run by the actix framework. The Context
type declaration can be used for more advanced implementations, but for now we can use the default implementation that is provided by actix itself.
Finally we implement the Handler
trait for the PlusOne
message that we defined earlier. In the handle()
method we increment the count
state and then return the new value to tell actix that this is our response to the message.
anchorRunning our CounterActor
While building the actor was relatively easy, running it is unfortunately still a little hard while Rust figures out its version of async/await
(see futures-await
).
The following code will startup our actor, send a message, wait for the response, send another message, wait for the response and finally exit the application:
let sys = actix::System::new("test");
let counter: Addr<Syn, _> = Arbiter::start(|_| CounterActor::new());
let counter_addr_copy = counter.clone();
let result = counter.send(PlusOne)
.and_then(move |count| {
println!("Count: {}", count);
counter_addr_copy.send(PlusOne)
})
.map(|count| {
println!("Count: {}", count);
Arbiter::system().do_send(actix::msgs::SystemExit(0));
})
.map_err(|error| {
println!("An error occured: {}", error);
});
Arbiter::handle().spawn(result);
sys.run();
The first thing to do when using actix actors is to set up a System
, that handles all those actor interactions for us. We do so by calling actix::System::new()
and passing it a name.
Next we start an [Arbiter
][artiter] in a new thread, that runs our CounterActor
. If that sounds like a foreign language to you, don't worry, I had the same feeling at first. For now all you need to know is that Syn
means that it is running in a separate thread, and that the Arbiter
is the thing that controls that thread.
The Arbiter::start()
call returns an Addr
(short for address), that we can use to talk to the actor. The Addr
struct has methods like send()
that can be used to send messages to the actor and receive their responses.
Rust is very strict around data ownership, and the "borrow checker" makes sure that data access can only happen in safe ways. Since we use the counter
variable for the first send()
call, we are (at least currently) not allowed to reuse it inside the callback. Instead we need to create a clone()
and use that one instead.
The large code structure in the middle of the snippet looks roughly like a Promise-chain in JavaScript, and it is exactly that. The counter.send()
call returns what Rust calls a Future
. A Future
(like a Promise
) has several methods that can be used to assemble a sort of pipeline of how to handle the result that the Future
will at some point return.
In this specific case we use .and_then()
to wait for the result of the PlusOne
message, then print it out to the console, and then fire off another PlusOne
message. Once that second message has returned we print the response again and then use a special system arbiter call to exit the process.
The major difference between a Promise
in JavaScript and a Future
in Rust is that a Promise
automatically runs when it is created, but a Future
needs to be started explicitly. This difference exists for performance reasons, and because in JavaScript there is no such thing as running on different threads.
To start the Future
that we have assembled we use the Arbiter::handle().spawn()
function, and then finally start the System
once everything is wired up correctly to block the current thread until all actors have finished running.
anchorSummary
This blog post covered some of the basic concepts of writing actors using the actix framework for Rust. In a follow-up post we will look into writing a small TCP client using these primitives, which can for example be used to forward traffic to websocket clients or just log the received messages to the console.