Patterns

Ask

The Ask pattern allows values to be sent by actors to outside of the actor system. The value is delivered as a Future.

Let's look at how this works:

  1. extern crate riker_patterns;
  2.  
  3. use riker_patterns::ask;
  4.  
  5. struct MyActor;
  6.  
  7. impl Actor for MyActor {
  8. type Msg = u32;
  9.  
  10. fn receive(&mut self,
  11. ctx: &Context<Self::Msg>,
  12. msg: Self::Msg,
  13. sender: ActorRef<Self::Msg>) {
  14.  
  15. // sender is the Ask, waiting to a message to be sent back to it
  16. sender.try_tell(msg * 2, Some(ctx.myself()));
  17. }
  18. }
  19.  
  20. fn main() {
  21. let model: DefaultModel<u32> = DefaultModel::new();
  22. let sys = ActorSystem::new(&model).unwrap();
  23.  
  24. let props = MyActor::props();
  25. let my_actor = sys.actor_of(props, "my-actor");
  26.  
  27. // ask returns a future that automatically is driven
  28. // to completion by the system.
  29. let res = ask(&sys, &my_actor, 100);
  30.  
  31. // the result future can be passed to a library or fuction that
  32. // expects a future, or it can be extracted locally using `block_on`.
  33.  
  34. let res = block_on(res).unwrap();
  35. println!("The result value is: {}", res);
  36. }

In the background Ask sets up a temporary intermediate actor that lives for the lifetime of the ask. Other actors see this temporary actor as the sender and can send a message back to it. When the temporary ask actor receives a message it fulfills the outstanding future and performs a stop on itself to cleanup.

Ask is particularly useful when you have part of an application that runs outside of the actor system, or in another actor system, such as a web server (e.g. Hyper) serving API requests. The resulting future can then be chained as part of the future stack.

Transform

Transform makes changing actor behavior based on its current state easier to reason about. Since actors maintain state, and indeed is a primary concern, being able to handle messages differently based on that state is important. The Transform pattern separates message handling by dedicating a receive function per state. This saves excessive matching to handle several possible states, i.e. handling behavior is pre-empted at the time of state change instead of on each message receive.

Info

If you're familair with Akka on the JVM, transform resembles become.

Example:

  1. #[macro_use]
  2. extern crate riker_patterns;
  3.  
  4. use riker_patterns::ask;
  5.  
  6. #[derive(Clone, Debug)]
  7. enum MyMsg {
  8. SetPassword(String), // password
  9. Authenticate(String), // password
  10. }
  11.  
  12. impl Into<ActorMsg<MyMsg>> for MyMsg {
  13. fn into(self) -> ActorMsg<MyMsg> {
  14. ActorMsg::User(self)
  15. }
  16. }
  17.  
  18. struct UserActor {
  19. username: String,
  20. password: Option<String>,
  21.  
  22. // rec field is required to store current method to be used
  23. rec: Receive<UserActor, MyMsg>,
  24. }
  25.  
  26. impl UserActor {
  27. fn actor(username: String) -> BoxActor<MyMsg> {
  28. let actor = UserActor {
  29. username,
  30. password: None,
  31. rec: Self::created, // <-- set initial method to `created` stated
  32. };
  33.  
  34. Box::new(actor)
  35. }
  36.  
  37. fn props(username: String) -> BoxActorProd<MyMsg> {
  38. Props::new_args(Box::new(UserActor::actor), username)
  39. }
  40.  
  41. /// Receive method for this actor when it is in a created state
  42. /// i.e. password has not yet been set.
  43. fn created(&mut self,
  44. ctx: &Context<MyMsg>,
  45. msg: MyMsg,
  46. sender: Option<ActorRef<MyMsg>>) {
  47.  
  48. match msg {
  49. MyMsg::SetPassword(passwd) => {
  50. self.password = Some(passwd);
  51.  
  52. // send back a result to sender
  53. // e.g. `sender.try_tell(Ok, None);`
  54.  
  55. // transform behavior to active state
  56. transform!(self, UserActor::active);
  57. }
  58. MyMsg::Authenticate(passwd) => {
  59. // `MyMsg::Authenticate` is invalid since no user password
  60. // has been set.
  61. // Signal that this is an error for the current state
  62. self.probe.as_ref().unwrap().0.event(ProbeMsg::Err);
  63. }
  64. }
  65. }
  66.  
  67. /// Receive method for this actor when a password has been set
  68. /// and the user account is now active.
  69. fn active(&mut self,
  70. ctx: &Context<MyMsg>,
  71. msg: MyMsg,
  72. sender: Option<ActorRef<MyMsg>>) {
  73.  
  74. match msg {
  75. MyMsg::Authenticate(passwd) => {
  76. // send back an authentication result to sender
  77. // e.g. `sender.try_tell(Ok, None);`
  78.  
  79. // signal that this is correct
  80. self.probe.as_ref().unwrap().0.event(ProbeMsg::Ok);
  81. }
  82. MyMsg::SetPassword(passwd) => {
  83. // set a new password
  84. self.password = Some(passwd);
  85. }
  86. }
  87. }
  88. }
  89.  
  90. impl Actor for UserActor {
  91. type Msg = MyMsg;
  92.  
  93. fn receive(&mut self,
  94. ctx: &Context<Self::Msg>,
  95. msg: Self::Msg,
  96. sender: Option<ActorRef<Self::Msg>>) {
  97.  
  98. // just call the currently set transform function
  99. (self.rec)(self, ctx, msg, sender)
  100. }
  101. }

Note

The transform! macro expects the field name of the current receive function on self to be named rec. It's easy to use a different name and either use your own macro, or just set the fuction using standard code. The advantage of transform! is that it is easy to read and identify when transformation is happening since it is distinct from standard code.