4

I'm looking for a central 'object' to which multiple tasks can 'subscribe' for async update messages.

fadedbee
  • 42,671
  • 44
  • 178
  • 308

3 Answers3

6

As far as I understand, EventEmitter is just a generic interface for event listeners support; objects which "implement" this interface provide several kinds of events on which the client code can put listener callbacks. These callbacks will be then called when corresponding event is emitted on the object's discretion. As JS is dynamically typed language, such interface arise very naturally and can be implemented by a lot of things.

First of all, neither in NodeJS nor in Rust you can "subscribe" tasks/threads: you put a listener callback on some object, and then this callback will be invoked from some thread, possibly even the current one, but in general the thread which subscribes to an object and the thread in which this callback will be run are different. In NodeJS there is a global event loop which calls into functions and external event listeners which in turn can invoke other event listeners, so you don't really know which thread will execute the listener. Not that you should care - event loop abstraction hides explicit threading from you.

Rust, however, is a proper multithreaded language. It does not run over a global event loop (though via libgreen it is possible - yet - to run Rust program in an event loop similar to the one in Node; it will be used for task management and I/O handling, but it will be separated from the libstd in the near future). Default Rust runtime, libnative, exposes facilities for creating native preemptively-scheduled threads and native I/O. This means that it does matter which thread eventually executes the callback, and you should keep in mind that all callbacks will be executed in the thread which owns the object, unless it creates separate threads specifically for event handling.

Another kind of problem with listeners is that Rust is statically typed language, and writing generic event listener interface is somewhat more difficult in statically typed languages than in dynamically typed ones, because you will need to write a sufficiently polymorphic interface. You would also want to take advantage of strong type system and make your interface as type safe as possible. This is not a trivial task. Sure, it is possible to use Box<Any> anywhere, but such an API wouldn't be very pleasant to work with.

So, at the moment there is no general-purpose event listener interface. There is no event bus library either. However, you can always write something yourself. If it is not very generic, it shouldn't be very difficult to write it.

Vladimir Matveev
  • 120,085
  • 34
  • 287
  • 296
1

I tried this way, and it worked fine with me, it is as simple as:

  • Define your object struct
  • Define your Listeners,
  • Define your standard functions, let's call them Extensions,
  • Define the add Emitter option to the Extensions by execution Self::Fn<Listener>

The same code I used in the playground is below, I just solved it in the rust forum:

// 1. Define your object
//#[derive(Debug)]
pub struct Counter {
 count: i32,
}

// 2. (Optional), if do not want to use `#[derive(Debug)]` 
//    you can define your own debug/release format
impl std::fmt::Debug for Counter {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Counter `count` is: {}", self.count)
    }
}

// 3. Define your Listeners trait 
trait EventListener {
     fn on_squared() {
        println!("Counter squared")
     }
     fn on_increased(amount: i32) {
        println!("Counter increased by {}", amount)
     }
     fn on_decreased(self, amount: i32);
}

// 4. Implement your Listeners trait to your object
impl EventListener for Counter {
    fn on_decreased(self, amount: i32) {
        println!("Counter reduced from {} to {}", &self.count, &self.count - amount)
    }
}

// 5. (Recommended), Define your standard functions/Extensions/Emitters
//    trait signatures
trait EventEmitter {
    fn square(&mut self);
    fn increase(&mut self, amount: i32);
    fn decrease(&mut self, amount: i32);
    fn change_by(&mut self, amount: i32);
}

// 6. Implement your standard functions/Extensions/Emitters trait to your object
impl EventEmitter for Counter {
    fn square(&mut self) { 
        self.count = self.count.pow(2);
        Self::on_squared();      // This is Event Emitter, calling the Listner
    }
    fn increase(&mut self, amount: i32) { 
        self.count = self.count + amount; 
        Self::on_increased(amount);   // This is Event Emitter, calling the Listner
    }
    fn decrease(&mut self, amount: i32) {
        let initial_value = self.count;
        self.count = self.count - amount;
        Self::on_decreased(Self {count: initial_value}, amount);  // This is Event Emitter, calling the Listner
    }
    fn change_by(&mut self, amount: i32) {
        let initial_value = self.count;
        self.count = self.count + amount;
        match amount {
            x if x > 0 => Self::on_increased(amount),   // This is Event Emitter, calling the Listner
            x if x < 0 => Self::on_decreased(Self {count: initial_value},  // This is Event Emitter, calling the Listneramount.abs()),
            _   => println!("No changes")
        }
    }
}

// 7. Build your main function
fn main() {
    let mut x = Counter { count: 5 };
    println!("Counter started at: {:#?}", x.count);
    x.square();   // Call the extension, which will automatically trigger the listner
    println!("{:?}", x);
    x.increase(3);
    println!("{:?}", x);
    x.decrease(2);
    println!("{:?}", x);
    x.change_by(-1);
    println!("{:?}", x);
}

And got the below output:

Counter started at: 5
Counter squared
Counter `count` is: 25
Counter increased by 3
Counter `count` is: 28
Counter reduced from 28 to 26
Counter `count` is: 26
Counter reduced from 26 to 25
Counter `count` is: 25
Hasan A Yousef
  • 22,789
  • 24
  • 132
  • 203
  • 1
    Thanks for the code, but that is not like NodeJS's EventEmitters - there is no run-time subscribe and an emitted event does not call a list of listeners. I will have a go at writing my own next week. – fadedbee Sep 18 '19 at 09:48
  • @fadedbee once you get something appreciate to share with me. – Hasan A Yousef Sep 18 '19 at 19:28
1

You can use crate event-emitter-rs. It provides an implementation exactly like in the standard events library from Node.Js.

use event_emitter_rs::EventEmitter;
use serde::{Deserialize, Serialize};
let mut event_emitter = EventEmitter::new();

event_emitter.on("Add three", |number: f32| println!("{}", number + 3.0));
event_emitter.emit("Add three", 5.0 as f32);
event_emitter.emit("Add three", 4.0 as f32);
// >> "8.0"
// >> "7.0"

// Using a more advanced value type such as a struct by implementing the serde traits
#[derive(Serialize, Deserialize)]
struct Date {
    month: String,
    day: String,
}

event_emitter.on("LOG_DATE", |date: Date| {
    println!("Month: {} - Day: {}", date.month, date.day)
});
event_emitter.emit("LOG_DATE", Date {
    month: "January".to_string(),
    day: "Tuesday".to_string()
});
// >> "Month: January - Day: Tuesday"
  • Thanks, that's interesting. Why does it have to serialise and deserialise everything which is emitted? In some cases this might be a performance issue. – fadedbee Nov 20 '22 at 15:55