├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── actor.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - nightly 5 | sudo: false 6 | script: 7 | - cargo build 8 | - cargo test 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wonder" 3 | version = "0.1.0" 4 | authors = ["Jamie Winsor "] 5 | description = "An Erlang/Elixir inspired actor library for Rust" 6 | repository = "https://github.com/reset/wonder" 7 | readme = "README.md" 8 | license = "MIT" 9 | 10 | [dependencies] 11 | time = "0.1" 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jamie Winsor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wonder [![Build Status](https://travis-ci.org/reset/wonder.png?branch=master)](https://travis-ci.org/reset/wonder) 2 | 3 | An [Erlang](http://www.erlang.org/doc/design_principles/gen_server_concepts.html)/[Elixir](http://elixir-lang.org/docs/stable/elixir/GenServer.html#content) inspired actor library for Rust 4 | 5 | ## Requirements 6 | 7 | * stable/nightly Rust 8 | 9 | ## Quick Start 10 | 11 | Define your actor 12 | 13 | ```rust 14 | pub struct MyActor; 15 | ``` 16 | 17 | Implement the GenServer trait 18 | 19 | ```rust 20 | impl GenServer for MyActor { 21 | type T = MyMessage; 22 | type S = (); 23 | type E = MyError; 24 | 25 | fn init(&self, _tx: &ActorSender, state: &mut Self::S) -> InitResult { 26 | // initialization implementation 27 | Ok(None) 28 | } 29 | 30 | // Implement overrides for default implementations for additional callbacks 31 | } 32 | ``` 33 | 34 | Start your actor 35 | 36 | ```rust 37 | use wonder::actor; 38 | 39 | fn main() { 40 | let actor = actor::Builder::new(MyActor).start(()).unwrap(); 41 | } 42 | ``` 43 | 44 | ## GenServer Trait 45 | 46 | To create a GenServer actor you need to create a struct (perhaps a [unit-like struct](https://doc.rust-lang.org/book/structs.html#unit-like-structs)?) and implement the GenServer trait. 47 | 48 | ``` 49 | struct MyActor; 50 | ``` 51 | 52 | ### Associated Types 53 | 54 | To implement the GenServer trait you need to define 3 [associated types](https://doc.rust-lang.org/book/associated-types.html) 55 | 56 | 1. `T` - A public enum type for messages to be sent to and from the GenServer. 57 | 1. `S` - The state which the actor will own and maintain. `()` can be provided in the case of an actor who will manage no state. 58 | 1. `E` - A public enum type for errors to be returned by your GenServer. 59 | 60 | ```rust 61 | struct MyState { 62 | pub initialized: bool, 63 | } 64 | 65 | #[derive(Debug)] 66 | enum MyError { 67 | DirtyState, 68 | } 69 | 70 | #[derive(Debug)] 71 | enum MyMessage { 72 | State(bool), 73 | GetState, 74 | SetState(bool), 75 | } 76 | 77 | impl GenServer for MyActor { 78 | type T = MyMessage; 79 | type S = MyState; 80 | // type S = (); // no state 81 | type E = MyError; 82 | 83 | // ... callbacks 84 | } 85 | ``` 86 | 87 | > note: It is required for both the custom error and message enums to derive the Debug trait. 88 | 89 | ### Callbacks 90 | 91 | The GenServer trait exposes 4 callbacks; one of which is required while the remaining three have default implementations making them optional to implement. 92 | 93 | ### `init/3 -> InitResult` 94 | 95 | > analogous to [GenServer:init/1](http://elixir-lang.org/docs/stable/elixir/GenServer.html#c:init/1) 96 | 97 | Handles initialization of the newly created actor. As with proccess initialization in Elixir/Erlang, this is a blocking call. This function will be called once while the actor is starting. 98 | 99 | The init function must returns an `InitResult` which is either `Ok(Option)` or `Err(E)` where E is your custom error type. The optional `u64` is the timeout value (in milliseconds) for the actor. 100 | 101 | ```rust 102 | impl GenServer for MyActor { 103 | type T = MyMessage; 104 | type S = MyState; 105 | type E = MyError; 106 | 107 | fn init(&self, atx: &ActorSender, state: &mut Self::S) -> InitResult { 108 | // perform some initialization 109 | Ok(None) 110 | } 111 | } 112 | ``` 113 | 114 | Parameters 115 | 116 | - `atx` - Channel sender for the running actor. This is equivalent to sending to "self" in Elixir/Erlang. 117 | - `state` - A mutable reference to the state that the running actor owns. 118 | 119 | > note: `InitResult` is analogous to the return tuple for the `init/1` callback in Elixir; ie. `{:ok, state}` or `{:stop, reason}` 120 | 121 | ### `handle_call/5 -> HandleResult` (optional) 122 | 123 | > analogous to [GenServer:handle_call/3](http://elixir-lang.org/docs/stable/elixir/GenServer.html#c:handle_call/3) 124 | 125 | Handles synchronous messages sent to the running actor. 126 | 127 | ```rust 128 | impl GenServer for MyActor { 129 | type T = MyMessage; 130 | type S = MyState; 131 | type E = MyError; 132 | 133 | // ... other callbacks ... 134 | 135 | fn handle_call(&self, msg: Self::T, tx: &ActorSender, 136 | atx: &ActorSender, state: &mut Self::S) -> HandleResult { 137 | match msg { 138 | MyMessage::GetState => HandleResult::Reply(MyMessage::State(state.initialized), None), 139 | MyMessage::SetState(value) => { 140 | state.initialized = value; 141 | HandleResult::Reply(MyMessage::Ok, None) 142 | } 143 | _ => HandleResult::Stop(StopReason::Fatal(String::from("unhandled call")), None), 144 | } 145 | } 146 | } 147 | ``` 148 | 149 | Parameters 150 | 151 | - `tx` - Channel sender for the caller who started the actor. This is equivalent to sending to the caller's PID in Elixir/Erlang. 152 | - `atx` - Same as `init/3`. 153 | - `state` - Same as `init/3`. 154 | 155 | > note: `HandleResult` is analogous to the return tuple for the `handle_call/3` callback in Elixir/Erlang. 156 | 157 | ### `handle_cast/4 -> HandleResult` (optional) 158 | 159 | > analogous to [GenServer:handle_cast/2](http://elixir-lang.org/docs/stable/elixir/GenServer.html#c:handle_cast/2) 160 | 161 | Handles asynchronous messages sent to the running actor. 162 | 163 | ```rust 164 | impl GenServer for MyActor { 165 | type T = MyMessage; 166 | type S = MyState; 167 | type E = MyError; 168 | 169 | // ... other callbacks ... 170 | 171 | fn handle_cast(&self, msg: Self::T, atx: &ActorSender, state: &mut Self::S) -> HandleResult { 172 | match msg { 173 | MyMessage::SetState(value) => { 174 | state.initialized = value; 175 | HandleResult::NoReply(None) 176 | }, 177 | _ => HandleResult::NoReply(None) 178 | } 179 | } 180 | } 181 | ``` 182 | 183 | Parameters 184 | 185 | - `atx` - Same as `init/3` 186 | - `state` - Same as `init/3` 187 | 188 | > note: `HandleResult` is analogous to the return tuple for the `handle_cast/2` callback in Elixir/Erlang. 189 | 190 | ### `handle_timeout/3 -> HandleResult` (optional) 191 | 192 | > analogous to matching `handle_info(:timeout, _state)` in Elixir 193 | 194 | Called when a timeout is set and the required amount of time has elapsed. A timeout can be set by including a timeout value in a `HandleResult` or `InitResult` returned by one of the GenServer callbacks. 195 | 196 | The timeout can be used for various reason, but a great example is a pattern to perform late initialization. If your actor has a long running initialization period you can timeout immediately and perform initialization within the `handle_timeout` callback. 197 | 198 | ```rust 199 | impl GenServer for MyActor { 200 | type T = MyMessage; 201 | type S = MyState; 202 | type E = MyError; 203 | 204 | fn init(&self, atx: &ActorSender, state: &mut Self::S) -> InitResult { 205 | Ok(Some(0)) 206 | } 207 | 208 | fn handle_timeout(&self, atx: &ActorSender, state: &mut Self::S) -> HandleResult { 209 | // long running function for late initialization 210 | HandleResult::NoReply(None) 211 | } 212 | } 213 | ``` 214 | 215 | Parameters 216 | 217 | - `atx` - Same as `init/3` 218 | - `state` - Same as `init/3` 219 | 220 | > note: `HandleResult` is analogous to the return tuple for the `handle_info/2` callback in Elixir/Erlang. 221 | 222 | ## Authors 223 | 224 | Jamie Winsor () 225 | -------------------------------------------------------------------------------- /src/actor.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::error::Error; 3 | use std::fmt; 4 | use std::fmt::Debug; 5 | use std::result; 6 | use std::sync::mpsc; 7 | use std::thread; 8 | use std::time; 9 | 10 | use time::{Duration, SteadyTime}; 11 | 12 | pub type InitResult = result::Result, E>; 13 | pub type ActorResult = result::Result; 14 | pub type StartResult = result::Result, E>; 15 | pub type ActorSender = mpsc::Sender>; 16 | pub type Receiver = mpsc::Receiver>; 17 | 18 | pub struct Actor 19 | where T: Any + Send 20 | { 21 | pub sender: ActorSender, 22 | pub receiver: Receiver, 23 | pub handle: thread::JoinHandle>, 24 | pub name: Option, 25 | } 26 | 27 | impl Actor 28 | where T: Any + Send 29 | { 30 | /// Create a new actor handler struct. 31 | pub fn new(sender: ActorSender, 32 | receiver: Receiver, 33 | handle: thread::JoinHandle>, 34 | name: Option) 35 | -> Self { 36 | Actor { 37 | sender: sender, 38 | receiver: receiver, 39 | handle: handle, 40 | name: name, 41 | } 42 | } 43 | 44 | pub fn cast(&self, message: T) -> ActorResult<()> { 45 | self::cast(&self.sender, message) 46 | } 47 | 48 | pub fn call(&self, message: T) -> ActorResult { 49 | self::call(&self.sender, &self.receiver, message) 50 | } 51 | } 52 | 53 | pub fn cast(tx: &ActorSender, message: T) -> ActorResult<()> { 54 | match tx.send(Message::Cast(message)) { 55 | Ok(()) => Ok(()), 56 | Err(err) => Err(ActorError::from(err)), 57 | } 58 | } 59 | 60 | pub fn call(tx: &ActorSender, rx: &Receiver, message: T) -> ActorResult { 61 | match tx.send(Message::Call(message)) { 62 | Ok(()) => { 63 | match rx.recv() { 64 | Ok(Message::Reply(msg)) => Ok(msg), 65 | Ok(_) => panic!("must reply from a call!"), 66 | Err(err) => Err(ActorError::from(err)), 67 | } 68 | } 69 | Err(err) => Err(ActorError::from(err)), 70 | } 71 | } 72 | 73 | pub struct Builder { 74 | name: Option, 75 | spec: T, 76 | } 77 | 78 | impl Builder { 79 | pub fn new(spec: A) -> Self { 80 | Builder { 81 | name: None, 82 | spec: spec, 83 | } 84 | } 85 | 86 | pub fn name(mut self, name: String) -> Builder { 87 | self.name = Some(name); 88 | self 89 | } 90 | 91 | /// Start an actor on a new thread and return an Actor. 92 | pub fn start(self, mut state: A::S) -> StartResult { 93 | let (otx, orx) = mpsc::channel::>(); 94 | let (itx, irx) = mpsc::channel::>(); 95 | let initial_wait_ms = match self.spec.init(&itx, &mut state) { 96 | Ok(result) => result, 97 | Err(err) => return Err(err), 98 | }; 99 | let itx2 = itx.clone(); // clone inner receive loop's sender for actor struct 100 | let name = self.name.clone(); 101 | let thread_name = name.clone().unwrap_or("GenServer".to_string()); 102 | let handle = thread::Builder::new() 103 | .name(thread_name.clone()) 104 | .spawn(move || { 105 | let mut timeout: Option = None; 106 | if let Some(ms) = initial_wait_ms { 107 | set_timeout(ms, &mut timeout); 108 | } 109 | loop { 110 | if let Some(go_time) = timeout { 111 | if go_time <= SteadyTime::now() { 112 | match self.spec.handle_timeout(&otx, &itx, &mut state) { 113 | HandleResult::Stop(reason, None) => { 114 | return shutdown(reason, None, &otx) 115 | } 116 | HandleResult::NoReply(Some(0)) => { 117 | set_timeout(0, &mut timeout); 118 | continue; 119 | } 120 | HandleResult::NoReply(new_timeout) => { 121 | if let Some(ms) = new_timeout { 122 | set_timeout(ms, &mut timeout); 123 | } 124 | } 125 | hr => { 126 | panic!("unexpected `HandleResult` returned from \ 127 | handle_timeout: {:?}", 128 | hr) 129 | } 130 | } 131 | } 132 | } 133 | match irx.try_recv() { 134 | Ok(Message::Call(msg)) => { 135 | match self.spec.handle_call(msg, &otx, &itx, &mut state) { 136 | HandleResult::Reply(msg, new_timeout) => { 137 | try!(otx.send(Message::Reply(msg))); 138 | if let Some(ms) = new_timeout { 139 | set_timeout(ms, &mut timeout); 140 | } 141 | } 142 | HandleResult::NoReply(new_timeout) => { 143 | if let Some(ms) = new_timeout { 144 | set_timeout(ms, &mut timeout); 145 | } 146 | } 147 | HandleResult::Stop(reason, reply) => { 148 | return shutdown(reason, reply, &otx) 149 | } 150 | } 151 | } 152 | Ok(Message::Cast(msg)) => { 153 | match self.spec.handle_cast(msg, &otx, &itx, &mut state) { 154 | HandleResult::Stop(reason, reply) => { 155 | return shutdown(reason, reply, &otx) 156 | } 157 | HandleResult::NoReply(new_timeout) => { 158 | if let Some(ms) = new_timeout { 159 | set_timeout(ms, &mut timeout); 160 | } 161 | } 162 | hr => { 163 | panic!("unexpected `HandleResult` returned from handle_cast: \ 164 | {:?}", 165 | hr) 166 | } 167 | } 168 | } 169 | Ok(hr) => panic!("received unexpected message type: {:?}", hr), 170 | Err(mpsc::TryRecvError::Disconnected) => { 171 | break; 172 | } 173 | Err(mpsc::TryRecvError::Empty) => {} 174 | } 175 | // This is absolutely the wrong solution. I need to park the thread or call 176 | // recv instead of try_recv and schedule the timeout mechanism another way. 177 | // This is a quick and dirty workaround that should be short lived while the API 178 | // stabilizes and is leveraged in our other applications. 179 | // 180 | // I'm so sorry for doing this. 181 | // - Jamie 182 | thread::sleep(time::Duration::from_millis(30)); 183 | } 184 | Ok(()) 185 | }) 186 | .unwrap(); 187 | Ok(Actor::new(itx2, orx, handle, name)) 188 | } 189 | } 190 | 191 | #[derive(Debug)] 192 | pub enum ActorError { 193 | InitFailure(String), 194 | AbnormalShutdown(String), 195 | SendError, 196 | RecvError, 197 | } 198 | 199 | impl From>> for ActorError { 200 | fn from(_err: mpsc::SendError>) -> Self { 201 | ActorError::SendError 202 | } 203 | } 204 | 205 | impl From for ActorError { 206 | fn from(_err: mpsc::RecvError) -> Self { 207 | ActorError::RecvError 208 | } 209 | } 210 | 211 | #[derive(Debug)] 212 | pub enum StopReason { 213 | Normal, 214 | Fatal(String), 215 | } 216 | 217 | #[derive(Debug)] 218 | pub enum HandleResult 219 | where T: Any + Send 220 | { 221 | Reply(T, Option), 222 | NoReply(Option), 223 | Stop(StopReason, Option), 224 | } 225 | 226 | pub enum Message 227 | where T: Any + Send 228 | { 229 | Call(T), 230 | Cast(T), 231 | Reply(T), 232 | } 233 | 234 | impl Debug for Message 235 | where T: Any + Send + Debug 236 | { 237 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 238 | match self { 239 | &Message::Call(ref msg) => write!(f, "CALL: {:?}", msg), 240 | &Message::Cast(ref msg) => write!(f, "CAST: {:?}", msg), 241 | &Message::Reply(ref msg) => write!(f, "REPLY: {:?}", msg), 242 | } 243 | } 244 | } 245 | 246 | pub trait GenServer: Send + 'static { 247 | type T: Send + Any + Debug; 248 | type S: Send + Any; 249 | type E: Error + 'static; 250 | 251 | fn init(&self, _tx: &ActorSender, state: &mut Self::S) -> InitResult; 252 | 253 | fn handle_call(&self, 254 | message: Self::T, 255 | _tx: &ActorSender, 256 | _me: &ActorSender, 257 | _state: &mut Self::S) 258 | -> HandleResult { 259 | panic!("handle_call callback not implemented; received: {:?}", 260 | message); 261 | } 262 | 263 | fn handle_cast(&self, 264 | message: Self::T, 265 | _tx: &ActorSender, 266 | _me: &ActorSender, 267 | _state: &mut Self::S) 268 | -> HandleResult { 269 | panic!("handle_cast callback not implemented; received: {:?}", 270 | message); 271 | } 272 | 273 | fn handle_timeout(&self, 274 | _tx: &ActorSender, 275 | _me: &ActorSender, 276 | _state: &mut Self::S) 277 | -> HandleResult { 278 | HandleResult::NoReply(None) 279 | } 280 | } 281 | 282 | fn set_timeout(wait_ms: u64, current_timeout: &mut Option) { 283 | *current_timeout = Some(SteadyTime::now() + Duration::milliseconds(wait_ms as i64)); 284 | } 285 | 286 | fn shutdown(reason: StopReason, 287 | reply: Option, 288 | sender: &ActorSender) 289 | -> Result<(), ActorError> { 290 | if let Some(msg) = reply { 291 | let _result = sender.send(Message::Reply(msg)); 292 | } 293 | match reason { 294 | StopReason::Normal => Ok(()), 295 | StopReason::Fatal(e) => Err(ActorError::AbnormalShutdown(e)), 296 | } 297 | } 298 | 299 | #[cfg(test)] 300 | mod tests { 301 | use super::*; 302 | use std::fmt; 303 | use std::error::Error; 304 | 305 | struct Worker; 306 | 307 | struct MyState { 308 | pub initialized: bool, 309 | } 310 | 311 | impl MyState { 312 | pub fn new() -> Self { 313 | MyState { initialized: false } 314 | } 315 | } 316 | 317 | #[derive(Debug)] 318 | enum MyError { 319 | DirtyState, 320 | } 321 | 322 | #[derive(Debug)] 323 | enum MyMessage { 324 | Ok, 325 | Stop, 326 | State(bool), 327 | GetState, 328 | SetState(bool), 329 | } 330 | 331 | impl GenServer for Worker { 332 | type T = MyMessage; 333 | type S = MyState; 334 | type E = MyError; 335 | 336 | fn init(&self, _tx: &ActorSender, state: &mut Self::S) -> InitResult { 337 | if state.initialized { 338 | Err(MyError::DirtyState) 339 | } else { 340 | state.initialized = true; 341 | Ok(None) 342 | } 343 | } 344 | 345 | fn handle_call(&self, 346 | msg: Self::T, 347 | _: &ActorSender, 348 | _: &ActorSender, 349 | state: &mut Self::S) 350 | -> HandleResult { 351 | match msg { 352 | MyMessage::Stop => HandleResult::Stop(StopReason::Normal, Some(MyMessage::Ok)), 353 | MyMessage::GetState => { 354 | HandleResult::Reply(MyMessage::State(state.initialized), None) 355 | } 356 | MyMessage::SetState(value) => { 357 | state.initialized = value; 358 | HandleResult::Reply(MyMessage::Ok, None) 359 | } 360 | _ => HandleResult::Stop(StopReason::Fatal(String::from("Nope")), None), 361 | } 362 | } 363 | 364 | fn handle_cast(&self, 365 | msg: Self::T, 366 | _: &ActorSender, 367 | _: &ActorSender, 368 | state: &mut Self::S) 369 | -> HandleResult { 370 | match msg { 371 | MyMessage::SetState(value) => { 372 | state.initialized = value; 373 | HandleResult::NoReply(None) 374 | } 375 | _ => HandleResult::NoReply(None), 376 | } 377 | } 378 | } 379 | 380 | impl fmt::Display for MyError { 381 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 382 | match *self { 383 | MyError::DirtyState => write!(f, "state already initialized"), 384 | } 385 | } 386 | } 387 | 388 | impl Error for MyError { 389 | fn description(&self) -> &str { 390 | match *self { 391 | MyError::DirtyState => "state already initialized", 392 | } 393 | } 394 | } 395 | 396 | #[test] 397 | fn error_on_init() { 398 | let mut state = MyState::new(); 399 | state.initialized = true; 400 | match Builder::new(Worker).start(state) { 401 | Err(MyError::DirtyState) => assert!(true), 402 | Ok(_) => assert!(false), 403 | } 404 | } 405 | 406 | #[test] 407 | fn call_set_get_state() { 408 | let state = MyState::new(); 409 | let actor = Builder::new(Worker).start(state).unwrap(); 410 | match actor.call(MyMessage::GetState) { 411 | Ok(MyMessage::State(true)) => assert!(true), 412 | _ => assert!(false), 413 | } 414 | assert!(actor.call(MyMessage::SetState(false)).is_ok()); 415 | match actor.call(MyMessage::GetState) { 416 | Ok(MyMessage::State(false)) => assert!(true), 417 | _ => assert!(false), 418 | } 419 | } 420 | 421 | #[test] 422 | fn multiple_cast_and_call_ordered() { 423 | let state = MyState::new(); 424 | let actor = Builder::new(Worker).start(state).unwrap(); 425 | assert!(actor.cast(MyMessage::SetState(false)).is_ok()); 426 | assert!(actor.cast(MyMessage::SetState(true)).is_ok()); 427 | assert!(actor.cast(MyMessage::SetState(false)).is_ok()); 428 | match actor.call(MyMessage::GetState) { 429 | Ok(MyMessage::State(result)) => assert_eq!(result, false), 430 | _ => assert!(false), 431 | } 432 | } 433 | 434 | #[test] 435 | fn stopping_an_actor() { 436 | let state = MyState::new(); 437 | let actor = Builder::new(Worker).start(state).unwrap(); 438 | match actor.call(MyMessage::Stop) { 439 | Ok(MyMessage::Ok) => assert!(true), 440 | _ => assert!(false), 441 | } 442 | match actor.handle.join() { 443 | Ok(_) => assert!(true), 444 | Err(_) => assert!(false), 445 | } 446 | } 447 | 448 | #[test] 449 | fn explicitly_naming_actor() { 450 | let state = MyState::new(); 451 | let actor = Builder::new(Worker).name("batman".to_string()).start(state).unwrap(); 452 | assert!(actor.name.is_some()); 453 | assert_eq!(actor.name.unwrap(), "batman".to_string()); 454 | } 455 | 456 | #[test] 457 | fn default_named_actor() { 458 | let state = MyState::new(); 459 | let actor = Builder::new(Worker).start(state).unwrap(); 460 | assert!(actor.name.is_none()); 461 | } 462 | } 463 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! An Erlang inspired actor library for Rust. 2 | 3 | extern crate time; 4 | 5 | pub mod actor; 6 | --------------------------------------------------------------------------------