Rust is very-very explicit. Whereas every event-driven framework is (usually) a full-throttle magical land of monkey patching or wrapping everything, plus the not so well advertised, but very forbidden dark marshes full of nasty blocking I/O goblins, and usually this means there's no proper API to use the async parts without the magic of the event loop.
In Rust you'd have to either make an few (unsafe) global ( https://github.com/rust-lang-nursery/lazy-static.rs ) structures to keep track of the event-driven state, or pass them into every callback, or make every callback somehow derive (or a derived from) a common structure that does this bookkeeping.
So far the language doesn't have syntactic sugar for this, but I think it'll be there in a few years. The compiler is up to the task (as you can already see a few 3rd party macro-driven solutions for similar "magic" things - such as serde's custom macros).
But since every event-driven thing can be implemented as a queue and a consumer thread pool (as it's implemented under the event-driven hood), I don't think Rust is a non-starter for even-driven solutions. Though I'm inclined to agree on the extra care needed to satisfy the type system to get easy callbacks is annoying.
I have to disagree that about the magical monkey patching. Event driven systems can be made to be very simple and easy to understand, and such that much boilerplate is avoided. It's just that most programmers are not capable of doing that, and the frameworks that become popular usually aren't subsequenty "fixed".
A few hints how to do it:
- no shared_ptr / ref counting, make object ownership clear
- design to allow destroying an object at any time even from a callback
- don't make callbacks harder than they need to be (use virtual functions or simple macro hackery to reduce boilerplate of using function pointers; don't introduce "signals and slots")
signals and slots seem like a way of introducing an extra layer of type safety and explicitness - you cannot accidentally pass the wrong function pointer to a handler, and it's easier to extract your wiring graph. they don't make things all that much harder either; they add a slight bit of ceremony, but it's worth it. (mostly based on my experience using qt)
At least in the Qt implementation, signals and slots make it easy to forget to connect essential signals, and to understand which signals are essential. I also feel like they tend to encourage making the interface more complex than it needs to be.
On the other hand, with function pointers or virtual functions, you can easily ensure a required callback is provided, by requiring the user to pass it in the constructor.
I don't see any difference related to "accidentally passing the wrong function". In either case you need to specify which function to call and on which object; for virtual function callbacks it's even easier and harder to fail. Type checking can also be done by the compiler in either case (even for function pointers at least in C++ it is possible to make a type-safe wrapper, see my implementation of lightweight callbacks [1]).
In Rust you'd have to either make an few (unsafe) global ( https://github.com/rust-lang-nursery/lazy-static.rs ) structures to keep track of the event-driven state, or pass them into every callback, or make every callback somehow derive (or a derived from) a common structure that does this bookkeeping.
So far the language doesn't have syntactic sugar for this, but I think it'll be there in a few years. The compiler is up to the task (as you can already see a few 3rd party macro-driven solutions for similar "magic" things - such as serde's custom macros).
But since every event-driven thing can be implemented as a queue and a consumer thread pool (as it's implemented under the event-driven hood), I don't think Rust is a non-starter for even-driven solutions. Though I'm inclined to agree on the extra care needed to satisfy the type system to get easy callbacks is annoying.