I'm always most curious with these frameworks how they're considering supervision. That's the real superpower of OTP, especially over Rust.
To me, Rust has adequate concurrency tooling to make ad hoc actor designs roughly on par with more developed ones for many tasks, but supervision is both highly valuable and non-trivial. Briefly, I'd say Rust is top-notch for lower-level concurrency primitives but lacks architectural guidance. Supervisor trees are a great choice here for many applications.
I've tried implementing supervision a few times and the design is both subtle and easy to get wrong. Even emulating OTP, if you go that route, requires exploring lots of quiet corner cases that they handle. Reifying this all into a typed language is an additional challenge.
I've found myself tending toward one_for_all strategies. A reusable Fn that takes some kind of supervisor context and builds a set of children, potentially recursively building the supervision tree beneath, tends to be the best design for typed channels. It forces one_for_all however as it's a monolithic restart function. You can achieve limited (batch-y) rest_for_one by having the last thing you boot in your one_for_all be another one_for_all supervisor, but this feels a little hacky and painful and pushes back against more granular rest_for_one designs.
You then probably want a specialized supervisor for one_for_one, similar to Elixir's DynamicSupervisor.
> That's the real superpower of OTP, especially over Rust.
That, and preemptive scheduling. And being able to inspect / debug / modify a live system. Man, these actor frameworks just make me appreciate how cool Erlang is.
It's clear that there's some beginning of this in place. They reference it as initial in the docs there and it's missing quite a bit. I'm not a huge fan of what I'm seeing here where each actor is implicitly itself and a supervisor (i.e., ActorRef makes reference to the actor's children). I think I saw that first in Akka and while it makes sense in theory, I don't like the practice.
To me, your supervision tree should be dedicated to that purpose and forms a superstructure relating entirely to spawning, resource provisioning, restarting, shutdown. Part of what makes it nice in Erlang is that it's consistent and thoughtless, just part of designing your system instead of being behavior you have to write or worry about much.
Here with Ractor they've built a special monitoring channel and callbacks into Actor (`handle_supervisor_evt`). This implies at some point one might write a nice supervisor in their framework that hopefully has some of those properties.
To me, Rust has adequate concurrency tooling to make ad hoc actor designs roughly on par with more developed ones for many tasks, but supervision is both highly valuable and non-trivial. Briefly, I'd say Rust is top-notch for lower-level concurrency primitives but lacks architectural guidance. Supervisor trees are a great choice here for many applications.
I've tried implementing supervision a few times and the design is both subtle and easy to get wrong. Even emulating OTP, if you go that route, requires exploring lots of quiet corner cases that they handle. Reifying this all into a typed language is an additional challenge.
I've found myself tending toward one_for_all strategies. A reusable Fn that takes some kind of supervisor context and builds a set of children, potentially recursively building the supervision tree beneath, tends to be the best design for typed channels. It forces one_for_all however as it's a monolithic restart function. You can achieve limited (batch-y) rest_for_one by having the last thing you boot in your one_for_all be another one_for_all supervisor, but this feels a little hacky and painful and pushes back against more granular rest_for_one designs.
You then probably want a specialized supervisor for one_for_one, similar to Elixir's DynamicSupervisor.