```rust
extern crate zestors;
use std::time::Duration; use zestors::{ actor_type::{Accepts, IntoAddress}, config::Config, error::{ExitError, RecvError}, process::{spawn, Address, Inbox}, request::{IntoRecv, Request, Rx}, };
//-------------------------------------------------------- // Step 1: Define the messages you will use //--------------------------------------------------------
// This message will be a simple message, without any reply.
struct MyMessage { number: u32, }
// Now we define a message which expects a reply of a String
.
struct Echo { string: String, }
//--------------------------------------------------------- // Step 2: Define the protocol of the actor //---------------------------------------------------------
// Here we define our protocol as an enum.
// It accepts 3 messages: Echo
, MyMessage
and i64
.
//
// This macro modifies the enum such that a reply can be sent back.
enum MyProtocol { Echo(Echo), MyMessage(MyMessage), Number(i64), }
async fn main() { //----------------------------------------------------------------------- // Step 3: Spawn the actor //-----------------------------------------------------------------------
// Here, we spawn the actor with a function from a default configuration.
//
// We get back a `Child<String, MyProtocol>` and a `Address<MyProtocol>`.
// These types are exactly the same as those gotten back from `spawn_actor`.
let (mut child, address) = spawn(
Config::default(),
|mut inbox: Inbox<MyProtocol>| async move {
loop {
match inbox.recv().await {
Ok(msg) => match msg {
// Here we receive the echo message, which wants back a reply!
// This reply can be sent back to the `Tx`, and was automatically
// created for us.
MyProtocol::Echo((Echo { string }, tx)) => {
println!("Echoing string: {}", string);
let _ = tx.send(string);
}
// The MyMessage does not have this `Tx`, as can be seen.
MyProtocol::MyMessage(MyMessage { number }) => {
println!("Received number: {}", number);
}
// And neither does the `u32`.
MyProtocol::Number(number) => {
println!("Received number: {}", number);
}
},
Err(e) => match e {
// Here we received a halt-signal, so we should exit now.
RecvError::Halted => break "Halted".to_string(),
// And if the inbox is closed and empty, we should also exit.
RecvError::ClosedAndEmpty => break "ClosedAndEmpty".to_string(),
},
}
}
},
);
//-------------------------------------------------
// Step 4: Send messages!
//-------------------------------------------------
// The following messages don't get a reply:
let _: () = address.send(10 as i64).await.unwrap();
let _: () = address.send(MyMessage { number: 11 }).await.unwrap();
// But our echo will get back a reply!
let rx: Rx<String> = address
.send(Echo {
string: "Hi there".to_string(),
})
.await
.unwrap();
// We can await the `Rx` to get it back:
let reply = rx.await.unwrap();
assert_eq!(reply, "Hi there".to_string());
// Or more concise:
let reply = address
.send(Echo {
string: "Hi there".to_string(),
})
.into_recv()
.await
.unwrap();
assert_eq!(reply, "Hi there".to_string());
//-------------------------------------------------
// (Step 4.1): Conversion of addresses
//-------------------------------------------------
// (Just skip to Step 5 if the readme is getting to long ;))
// Now you might be wondering, what is the use of all these complicated traits?
// Well, we can convert our `Address<MyProtocol>` into an `DynAddress![i64, Echo]`.
// This conversion is checked at compile-time, so it is impossible for errors to
// occur at runtime.
let address: DynAddress![i64, Echo] = address.into_dyn();
address.send(10 as i64).await.unwrap();
// This address can be transformed further:
let address: DynAddress![Echo] = address.transform();
let _tx = address
.send(Echo {
string: "Hi".to_string(),
})
.await
.unwrap();
// And it can finally be converted back into our original address:
let address: Address<MyProtocol> = address.downcast().unwrap();
address.send(10 as i64).await.unwrap();
//-------------------------------------------------
// (Step 4.2): More cool things
//-------------------------------------------------
// What is so great about this?
//
// We can now transform addresses with different types into the same ones.
// As long as an address accepts the message `u32`, it can be transformed into
// an `Address![u32]`. Some examples of this usage:
async fn accepts_u32_v1(address: DynAddress![i64, Echo]) {
address.send(10 as i64).await.unwrap();
}
async fn accepts_u32_v2<T>(address: T)
where
T: IntoAddress<DynAccepts![i64, Echo]>,
{
let address: DynAddress![i64, Echo] = address.into_address();
address.send(10 as i64).await.unwrap();
}
async fn accepts_u32_v3<T>(address: Address<T>)
where
T: Accepts<i64> + Accepts<Echo>,
{
address.send(10 as i64).await.unwrap();
}
// v1 must be used with a dynamic address
accepts_u32_v1(address.clone().into_dyn()).await;
// v2 can also be used with a static address
accepts_u32_v2(address.clone()).await;
accepts_u32_v2(address.clone().into_dyn::<DynAccepts![Echo, i64]>()).await;
// Just like v3
accepts_u32_v3(address.clone()).await;
accepts_u32_v3(address.clone().into_dyn::<DynAccepts![Echo, i64]>()).await;
// All of these functions can be used with addresses of different protocols. This
// principle allows for building generic solutions that work for different types of
// addresses.
// This is done without any overhead for normal message sending. Messages are NOT
// boxed before being sent, but the normal `Protocol` is sent through the channel.
//-------------------------------------------------
// Step 5: Shutting down the actor
//-------------------------------------------------
// Now we would like our actor to shutdown gracefully again. Luckily there is builtin
// functionality for this.
// Since we used a default config, we could drop the `Child`, which would halt and abort
// the actor, and cause for a graceful exit. But there is an even better way to do this:
// We will give the child 1 second to shut down before we try to abort it.
match child.shutdown(Duration::from_secs(1)).await {
Ok(exit) => {
// Now, it should have exited with the string "Halted".
assert_eq!(exit, "Halted");
}
Err(error) => match error {
ExitError::Panic(_) => panic!("Actor exited because of a panic"),
ExitError::Abort => panic!("Actor exited because it was aborted"),
},
}
} ```