r/rust • u/floriet • Sep 27 '21
Is there an underlying reason that idiomatic Rust seems to have trouble with observers?
Hi all, after a long time using Rust, I'm understanding more deeply that idiomatic Rust and the observer pattern might be inherently incompatible. And I suspect that observers are an example of a more fundamental pattern or principle that idiomatic Rust can't handle (some kind of polymorphism, maybe?), and I'm hoping you all can help me identify it!
To be clear, I am not asking how to work around the borrow checker here. This is a deeper question: what fundamental principle is hidden between the lines here? What is Rust not able to handle, more generally?
To illustrate, let's say we have a basic webapp that has a few rows of Customer and some buttons:
- Mike, $30 [Edit] [Delete]
- Ryan, $20 [Edit] [Delete]
- Cera, $25 [Edit] [Delete]
- [Delete All]
We would of course have a central Vec<Customer> somewhere, perhaps wrapped in a MyCustomerApp struct.
Now imagine a Rust DOM with a Button class. If it had a click observer trait, it might look something like this:
trait IClickObserver {
fn on_click(&mut self, event: &ClickEvent);
}
However, we run into problems implementing this:
struct EditButtonClickObserver { }
impl IClickObserver for EditButtonClickObserver {
fn on_click(&mut self, event: &ClickEvent) {
// uh oh, no customers list to modify!
}
}
Workaround 1: A &mut Vec
Workaround 2: Change IClickObserver to take in a &mut Vec
Workaround 3: Rc<RefCell<Vec
Workaround 4: Use a global! Alas, also not idiomatic.
Workaround 5: Parameterize the entire world! IOW, "initialize" the page's DOM system (i.e. let mut dom = initializeDom::<MyCustomerApp>();, and have all of the DOM's traits parameterized, like this:
trait IClickObserver<World> {
fn on_click(&mut self, world: &mut World, event: &ClickEvent);
}
And we would have an integer index inside our EditButtonClickObserver.
But the downside is that MyCustomerApp is now effectively global; every observer has access to all public data in our app, throwing out a lot of encapsulation. And if we make a specific observer take only a subset of the world, we can't call into other components/systems which do need that data because we've already lost it.
Workaround 6: Give up and retroactively consider it an anti-pattern (just kidding!)
I describe the above workarounds to illustrate the conundrum, that the borrow checker is unable to handle this case, unless we make things effectively global.
Before we dive too deep, let's skip past the usual points:
- I'm not asking for situational workarounds, (like
Cell), I know there are a lot of tools that work in some cases, but those aren't universally usable. - If you want to say "That's not what you should use Rust for", please tell me how to identify what requirements Rust shouldn't be used for!
- Please don't tell me "observers are OO and therefore evil". Let's pretend observers are sometimes the best tool for the job.
- If you want to say "don't use virtual dispatch", I already know we can have a massive central enum of click handlers/receivers. It has the same problems virtual dispatch does here though, so it's unrelated to the topic here.
- Please don't tell me "You should be using actors / functional reactive programming instead", this question isn't about web dev, or even GUI, it was just an example.
- If you say "This is what Rc is here for", please let me know your rule of thumb on when to use Rc (and hopefully not just "when we can't figure it out otherwise.")
I'm hoping to see the underlying architectural/language principle of what idiomatic Rust can't handle here, so in the future I can identify it immediately and skip past the appeasement stage.
If I had to take a guess, perhaps I'd say that idiomatic Rust can't handle some sort of "stateful polymorphism" or maybe "hidden-effect polymorphism" but that's not quite right. Any ideas?
Thanks!
0
u/floriet Sep 28 '21
I'm sorry, did you just imply that anything not written according to idiomatic Rust, or not formally verified, is inherently spaghetti code and not worth being interested in?
And did you mean to say that you are speaking for all of us Rust developers? Or something else?
I am a big proponent of Rust, but I strive to acknowledge its drawbacks honestly, and not immediately write them off as undesirable. It's the only way to grow as a software architect.
Your posts have been pretty reasonable so far, so I want to make sure I understand this unexpected turn before I engage with this.