No one has addressed the actual issue here, unfortunately. There's a good reason why you'll often find message passing done with enums in Rust where other languages use closures, and it's not ideological.

In Rust, if you want to access shared state, you need to hold onto a reference to it. If a reference captured by a closure is unique, the compiler conservatively prevents you from not only accessing that variable, but anything that could transitively lead to it, and if it's shared, the compiler prevents you from mutating anything that could lead to it. Additionally, you can't move or destroy the object until there's no chance any value transitively referenced through it is still borrowed--which across threads, often means pretty much no borrow length will suffice. In a UI framework sending stuff across threads, that's a totally unacceptable state of affairs. By contrast, enums that declaratively specify what you want to modify have no such issues, because the state only needs to be accessed when you actually want to run the event.

There are three ways to work around the lifetime part, but they're not ideal. The most common is to use something like Rc or Arc, which allow reference counting (a primitive form of garbage collection). Reference counting always makes your objects shared, so it guarantees you'll have to tackle the mutation problem somehow, which can be unsatisfying; it also has a runtime cost (which can be quite significant) and can cause memory leaks if you have cycles in your data (though this is rarely an issue in UI frameworks). Secondly, you can architect your application around things like arenas, which give you "scoped" access to lifetimes--these can be really efficient with the right use case, especially pointer-spaghetti graphs, but can restrict a lot of the patterns you'd write in Rust, since the arenas have to outlive everything you want to do with the objects. Finally, you can separate your shared data from the closure--which works great but now you are restricted to "bare" functions or functions that only capture unrelated state. The closure can't specify the state it wants to update, it has to be directed to the object--which is much less ideal (since now the closures aren't universal in any way, as they need to be passed in the state they want to modify).

You can work around the mutation part in two ways, but neither is that great. One is, again, to ban closures from capturing any environment state, so the data is still stored separately and has to be passed in, which has the limitations mentioned previously.

The other is to use "interior mutability" which uses some combination of runtime checks and other restrictions to guarantee that you're only accessing the element one at a time. Almost invariably, these mechanisms are slower at runtime, restrict your types somehow (e.g. making them non thread safe), can cause your program to panic if you misuse them, or are just plain annoying to use (or some combination of all of these). So when you can avoid this option, you do!

So basically: enums make a ton of sense in Rust because they totally avoid these borrow checker issues. Your intuitions about using closures for this stuff in functional or garbage collected languages don't apply here--patterns that make a lot of sense in them don't work at all in Rust. That's one big reason there are so many Rust UI frameworks rather than just wrapping the C ones or using the same patterns you see in functional languages--the usual patterns simply don't work well.

That's not say there aren't some upsides to the approach from a reasoning standpoint. A big one is that it enforces "single writer" control flow--just one place is responsible for all the state updates, so you don't have to worry about synchronization weirdness due to lots of places updating the state on their own. IME, this helps a lot with reasoning. But not everyone agrees that that outweighs the convenience of being able to use a callback, which I totally get--it's just that the "convenient way" isn't really convenient in Rust at all. And it's this that causes all these libraries to be designed this way (at least that's been my experience), not theoretical concerns about serialization and stuff which I agree is basically a nonissue in practice.