[HN Gopher] Ractor - a Rust Actor Framework
       ___________________________________________________________________
        
       Ractor - a Rust Actor Framework
        
       Author : todsacerdoti
       Score  : 136 points
       Date   : 2024-11-03 01:47 UTC (21 hours ago)
        
 (HTM) web link (slawlor.github.io)
 (TXT) w3m dump (slawlor.github.io)
        
       | jatins wrote:
       | That looks interesting! What's the distributed story of Ractor?
       | Would you need a central store like Redis to serve as Actor
       | registry?
       | 
       | One of the promises of Elixir/Erlang is that you can call a
       | process/Actor on different machine just like you can one on same
       | once you put together a bunch of machines in a cluster
        
         | stefanka wrote:
         | Seems to be supported
         | 
         | > Additionally ractor has a companion library, ractor_cluster
         | which is needed for ractor to be deployed in a distributed
         | (cluster-like) scenario. ractor_cluster shouldn't be considered
         | production ready, but it is relatively stable and we'd love
         | your feedback!
        
         | kemiller wrote:
         | Elixir/erlang/gleam also have the advantage that many libraries
         | are written as servers and so automatically gain the benefits
         | and resilience. Something all these actor frameworks can't give
         | you.
        
         | brunoqc wrote:
         | With gleam, how do you call an actor on another machine when
         | you have a cluster? Do you need something like redis?
        
           | doawoo wrote:
           | The Erlang runtime and EPMD takes care of this for you. Every
           | node in the cluster gets a name, so you can address it
           | directly, and, every PID is unique across nodes on a cluster,
           | so you can send messages to processes (actors) no matter
           | where they are in the cluster.
           | 
           | OTP is super powerful out of the box :)
        
             | brunoqc wrote:
             | Thanks! I'm taking a look at
             | https://github.com/wmealing/gleam-distribution-
             | demo/tree/mai...
             | 
             | And
             | 
             | https://github.com/wmealing/gleam-otp-design-
             | principals/blob...
        
       | victorbjorklund wrote:
       | This is nice. Any way to also make it an Erlang complient node so
       | it can be called from a erlang cluster?
        
         | jerf wrote:
         | Making something Erlang-compliant is actually fairly heavy
         | duty. It's more than just "you've got actors, I've got actors,
         | let's play together!"... you _have_ to support the Erlang term
         | format _specifically_ , so on the Rust side you would need to
         | be able to convert to and from all the state you actually want
         | into Erlang terms, including PID references, you need to
         | support Erlang's linking capability, you need to support
         | Erlang's name server lookups, you need to support Erlang's
         | _specific_ messaging semantics for out-of-order message
         | handling and mailboxes, binary sharing, probably some other
         | things I 'm not remembering. It's not an impossible amount of
         | work but it's not in the "couple of weeks of spare time for a
         | motivated developer" range either.
         | 
         | Most people most of the time are just better hooking up one of
         | the many, many other event busses that both Rust and Erlang can
         | speak to and working across that.
        
           | sph wrote:
           | Not only that, but you need a full BEAM-compatible VM, as you
           | can send entire processes across nodes.
        
             | throwawaymaths wrote:
             | No, there are cnodes.
        
             | derefr wrote:
             | No you can't.
             | 
             | The closest you could get to this would be:
             | 
             | 1. assuming a running process pA on nodeA, registered in a
             | cluster-level process registry by name N;
             | 
             | 2. send the process pA's state to a function on node B,
             | which will use it to start a process pB on node B that is a
             | clone of pA;
             | 
             | 3. update the cluster-level process registry to point N at
             | pB.
             | 
             | 4. if you want to be really clever -- tail-call (or code-
             | upgrade) pA out of its existing loop, into code that makes
             | pA act as a proxy for pB, so that anything sending a
             | message to pA ends up talking to pB through pA.
             | 
             | But this isn't "sending a process"; pA itself remains
             | "stuck" on nodeA (node IDs are actually embedded in PIDs!),
             | and there's especially no native mechanism that would do
             | things like rewrite the links/monitors on other processes
             | that currently point to pA, to instead point to pB (so if
             | pA was a supervisor, you couldn't "re-parent" the children
             | of pA over to pB.)
        
       | a-dub wrote:
       | how does it compare with bastion?
        
       | rapsey wrote:
       | Sorry for being so negative but why do people keep building these
       | Actor frameworks for Rust? None of them get any usage.
        
         | bowsamic wrote:
         | It's kind of like Java. It's a way to feel like you're doing
         | work without actually doing anything
        
           | rochak wrote:
           | Couldn't have said it any better myself
        
             | vdvsvwvwvwvwv wrote:
             | Except it makes no sense. Java is a language that, while
             | idiomatically a bit boilerplatey, is generally a workhorse
             | for getting shit done.
        
               | ahoka wrote:
               | It's everything but that. It's the instrument of over
               | engineering. Maybe not the language itself (it was the
               | Rust if its time, everyone wanted to use it for
               | everything, even if it made no sense), but the whole
               | ecosystem just prefers complexity and breaking changes.
               | Maybe it's a coping mechanism for devs stuck with it, or
               | I don't know.
        
               | Ygg2 wrote:
               | > it's the instrument of over engineering.
               | 
               | True, but find me one language that someone, somewhere
               | didn't over- something. Either over-engineer or over-
               | simplify, or over-use.
               | 
               | People get excited about technology and try to push the
               | envelope on its usage. That's true of any widely used
               | tech.
               | 
               | Hitting the golden middle is notoriously hard, especially
               | when it is not the same middle for everyone.
               | 
               | > the whole ecosystem
               | 
               | What precisely do you mean? Sure Spring is notorious for
               | this, but not the wider ecosystem.
               | 
               | Sure some libraries might not be using SemVer, but Maven
               | itself predates it as well.
        
               | bowsamic wrote:
               | No I mean in Java it's common to write extensive
               | boilerplate and abstractions just bc it feels
               | constructive. Java is extremely useful, but business use
               | of it is not so nice
        
           | pjmlp wrote:
           | Well my phone, TV and blue ray player do lots of useful
           | stuff.
        
             | ahoka wrote:
             | Displaying ads?
        
               | pjmlp wrote:
               | Powered by Java, in an a market share Desktop Linux will
               | never achieve.
        
               | talldayo wrote:
               | Linux has a license that protects it from the class of
               | no-holds-barred codebase abuse Java suffers from, for
               | one.
        
               | pjmlp wrote:
               | Apparently OEMs aren't aware of such marvel.
        
               | talldayo wrote:
               | They check in periodically, mostly for posterity:
               | https://github.com/teslamotors/linux
               | 
               | Nobody can blame them for choosing the right tool for the
               | job, but many held them in contempt for license
               | violation.
        
         | seanhunter wrote:
         | For reasonable reasons in my view.
         | 
         | The actor model is imo a great way of doing concurrency in the
         | absence of the data race guarantee that safe rust provides at
         | compile time. If you know you have no data races, I don't think
         | actors give you that much.
         | 
         | That said, some people just really like actors as a mental
         | model and/or they want to interoperate with actor-based systems
         | written in other languages or provide an actor substrate
         | written in rust that will be embedded into another language
         | perhaps? It's definitely a niche usage.
        
           | t-writescode wrote:
           | > That said, some people just really like actors as a mental
           | model
           | 
           | This is me, for sure. "little box does one thing, processes
           | one thing at a time, maintains the state of one thing, talks
           | to different pieces of the system through this predefined
           | method", etc, that actors give me is very easy for me to
           | reason about and work with, so I use it and love it.
        
         | biorach wrote:
         | > None of them get any usage.
         | 
         | Github stats show some uptake so clearly someone finds them
         | useful
        
           | dietr1ch wrote:
           | I don't think it's a good metric, does it get stars because
           | it's cool, or because people are building things with it
           | while taking advantage of the Actor model?
        
             | biorach wrote:
             | well it's more of a metric than you've provided
        
               | dietr1ch wrote:
               | Why do you get so defensive?
               | 
               | Also, didn't what I said about usage better map to number
               | of dependent crates or downloads by cargo? Both are
               | listed on https://crates.io/crates/ractor
        
         | babo wrote:
         | This seems to be used at Meta: "Ractor had a session at
         | RustConf'24 about how it's used for distributed overload
         | protection in Rust Thrift servers at Meta."
        
           | JW_00000 wrote:
           | Their presentation [1] contains a few slides with the
           | motivation for this framework (they have existing Thrift
           | services in C++ and Python, and want to start using Rust too)
           | and why actors (answer: they scale better than a naive
           | solution?). Also a slide "why a new framework" when so many
           | already exist for Rust. (Answer: many are dead/unsupported,
           | are too far from Erlang principles, not flexible enough, or
           | use custom runtimes while ractor builds on Tokio.)
           | 
           | [1] https://slawlor.github.io/ractor/assets/rustconf2024_pres
           | ent...
        
             | mtndew4brkfst wrote:
             | Tiny bit of context I'd also call out is that one of the
             | primary authors comes from WhatsApp IIRC, with an extensive
             | Erlang background.
        
         | stefanka wrote:
         | Can it be used for distributed computation for ML or data
         | science tasks (eg., like what dask does for Python)?
        
           | jerf wrote:
           | Actors are better suited for highly heterogeneous task sets,
           | where am actor or small set of them correspond to some task
           | and you may have thousands or more.
           | 
           | Homogenous tasks should use an approach that is aware of and
           | takes advantage of the homogeneity, e.g., the sort of
           | specific optimizations a framework might make to orchestrate
           | data flows to keep everything busy with parallel tasks.
           | 
           | You can use actors for orchestration, but you're really just
           | using them because they're there, not because they bring any
           | special advantages to the task. Any other solution that works
           | is fine and there would never be a particular reason to
           | switch to actors if you already had a working alternative.
        
             | stefanka wrote:
             | Are you aware of any framework or could use for such tasks
             | (ML and alike)?
        
         | dietr1ch wrote:
         | I guess it's a bit hard to change the way of thinking and
         | structuring programs/applications. We mostly do either blocking
         | procedural things, or web servers. Maybe actors can help remove
         | the network from web servers where they add unnecessary
         | complexity/overhead.
         | 
         | At the same time Web servers seem a bit better as I don't
         | really want to be figuring out how many actors of each "class
         | of actor" to spawn and to maybe babysit each of them, and for
         | debugging the web stack has more tooling and is better
         | understood than any particular actor system. The advantages
         | seem mostly out of experience and a lot of time. Maybe some
         | iteration of an Actor framework will render Web servers too
         | quirky/unsafe/slow/complex.
        
         | the8472 wrote:
         | I've used something in the general shape of actors (though
         | mostly handrolled) when implementing websocket APIs. If I had
         | to wire up lots of or several layers of those then using a
         | library for that would start to make sense.
        
         | eviks wrote:
         | Because usage isn't the only motivator? Because they hope
         | they'll get usage (can yuo see the future that non _will_ get
         | any usage?)?
        
         | riquito wrote:
         | > Sorry for being so negative but why do people keep building
         | these Actor frameworks for Rust? None of them get any usage.
         | 
         | There are good answers already on the actual "why", but I'd
         | like to point out that "none of them get any usage" is a much
         | better reason to build a new one than "there's already a
         | library dominating the ecosystem". Clearly none of the existing
         | libraries, for one reason or another, have been deemed
         | interesting enough, let's see someone else try
        
           | 0x457 wrote:
           | I think it's an (so-far) endless cycle of:
           | 
           | - One decides that they want to use actors
           | 
           | - Look for frameworks
           | 
           | - find those that either abandoned or don't vibe with one
           | 
           | - start your own
           | 
           | - passion runs out for project that needed actors
        
       | simonask wrote:
       | Ractor is cool, but I've been wondering why it uses the async
       | `async_trait` rather than native async traits. Is it just because
       | it came out before async traits were stabilized, and now code
       | relies on it and it would be a breaking change to migrate?
       | 
       | For context, the `async_trait` crate makes futures from trait
       | methods that are wrapped in `Pin<Box<dyn Future<...>>>`, which
       | means that every async call to a trait method must make a heap
       | allocation. This is currently a necessary thing to do if async
       | trait methods were invoked with dynamic dispatch (through `dyn
       | Actor`), but the `Actor` trait has associated methods, so that is
       | already not generally possible.
       | 
       | I realize that methods on the `Actor` trait return futures that
       | are `Send`, but specifically for an actor framework that feels
       | like a very specific design choice that isn't universally good or
       | necessary. Another design would give let the spawned task that
       | executes the actor's messages exclusive access to the actor (so
       | `handle()` could take `&mut self`).
       | 
       | I've ended up implementing a simple alternative design in my own
       | project (it's not fundamentally very hard, but doesn't have all
       | the features, like supervision) because the per-message heap
       | allocations and internal locking became wasteful for my use case.
        
         | SkiFire13 wrote:
         | > I've been wondering why it uses the async `async_trait`
         | rather than native async traits
         | 
         | From their crate documentation[0]:
         | 
         | > The minimum supported Rust version (MSRV) is 1.64. However if
         | you disable the `async-trait` feature, then you need Rust >=
         | 1.75 due to the native use of `async fn` in traits.
         | 
         | [0]: https://docs.rs/ractor/0.13.0/ractor/index.html
        
       | k3vinw wrote:
       | There's also actix: https://github.com/actix/actix
        
         | lionkor wrote:
         | I use actix_web for all my web server stuff, its wonderful,
         | super fast, really easy to use and not that easy to fuck up.
        
           | hobofan wrote:
           | I know it seems unintuitive but actix-web is largely
           | unrelated to actix nowadays: https://actix.rs/docs/whatis/
        
         | mtndew4brkfst wrote:
         | It's been informally discouraged for use in net-new projects
         | via a chat message from one of the maintainers during this
         | year's RustConf.
         | 
         | https://discord.com/channels/734893811884621927/127043967262...
         | 
         | Actix-web remains healthy in this regard, it was specifically
         | just about the actix crate.
        
           | Capricorn2481 wrote:
           | This link just goes to a blank discord page for me as if
           | something is trying to load. I don't think these are
           | shareable if you aren't in the server.
        
       | qwertox wrote:
       | Related from one month ago: Kameo -
       | https://github.com/tqwewe/kameo
       | 
       |  _Show HN: Kameo - Fault-tolerant async actors built on Tokio_ -
       | https://news.ycombinator.com/item?id=41723569 - October 2024 (58
       | comments)
        
       | hit8run wrote:
       | Well someone got inspired by Rubys Ractors hence the naming?
       | 
       | https://docs.ruby-lang.org/en/master/ractor_md.html
        
         | mtndew4brkfst wrote:
         | Unlikely, the contributors don't seem to have meaningful Ruby
         | backgrounds. Isn't it a simpler supposition that the naming
         | intent was merely (R)ust Actors?
        
         | rubyfan wrote:
         | Yeah bad choice of naming here. Oddly this project shows up
         | first on DDG for "ractor" despite prevalent Ruby ractor
         | documentation.
        
         | pshc wrote:
         | I have to wonder if Ractor was inspired from _The Diamond Age_.
        
       | Sytten wrote:
       | I personnaly don't like the single enum model for messages. I
       | prefer the generic Handler trait model.
       | 
       | Also another square that I have not circled with async actor
       | other than actix is that all of them use mutable self and they
       | dont have a great way to have long running tasks. Sometimes you
       | would want to call a remote server without blocking the actor, in
       | actix this is easy to do and you unamed child actors to do that.
       | All those newer frameworks don't have the primitives.
        
         | echelon wrote:
         | It looks like the message type can be anything:
         | type Msg = MyFirstActorMessage;
        
         | derefr wrote:
         | > all of them use mutable self
         | 
         | As demonstrated in the linked tutorial, Ractor passes its
         | handlers `&self` with `&mut State`.
        
       | ninkendo wrote:
       | At $dayjob I've taken to just implementing my own actor-like
       | model when I need interior mutability across tasks. Something
       | like:                   struct State { counter: usize, control:
       | mpsc::Receiver<Msg> }         struct StateActor { addr:
       | mpsc::Sender<Msg> }         enum Msg {             Increment {
       | reply: oneshot::Sender<()> }         }              impl
       | StateActor {             pub async fn increment(&self) {
       | let (tx, rx) = oneshot::channel();                 let msg =
       | Msg::Increment { reply: tx };
       | self.addr.send(msg).await.unwrap();
       | rx.await.unwrap();             }         }              impl
       | State {             fn start(self) {
       | tokio::spawn(async move {                     /* ...
       | tokio::select from self.control in a loop, handle messages, self
       | is mutable */                     /* e.g. self.counter +1 1;
       | msg.reply.send(()) */                 })             }         }
       | // in main         some_state_actor.increment().await // doesn't
       | return until the message is processed
       | 
       | A StateActor can be cheaply cloned and used in multiple threads
       | at once, and methods on it are sent as messages to the actual
       | State object which loops waiting for messages. Shutdown can be
       | sent as an in-band message or via a separate channel, etc.
       | 
       | To me it's simpler than bringing in an entire actor framework,
       | and it's especially useful if you _already_ have control loops in
       | your program (say, for periodic work), and want an easy system
       | for sending messages to /from them. That is to say, if I used an
       | existing actor framework, it solves the message sending/isolation
       | part, but if I want to do my own explicit work inside the
       | tokio::select loop that's not strictly actor message processing,
       | I already have a natural place to do it.
        
       | tel wrote:
       | 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.
        
         | pton_xd wrote:
         | > 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.
        
         | 0x457 wrote:
         | Here is how it explained in the docs:
         | https://docs.rs/ractor/latest/ractor/actor/index.html#actor-...
        
       | whytevuhuni wrote:
       | What's the benefit of having a State type, instead of using
       | _handle( &mut self)_, _pre_start() - > Self_, and putting the
       | state inside the type that implements the actor?
        
         | derefr wrote:
         | From the README (https://github.com/slawlor/ractor?tab=readme-
         | ov-file#differe...):
         | 
         | > When designing ractor, we made the explicit decision to make
         | a separate state type for an actor, rather than passing around
         | a mutable self reference. The reason for this is that if we
         | were to use a &mut self reference, creation + instantiation of
         | the Self struct would be outside of the actor's specification
         | (i.e. not in pre_start) and the safety it gives would be
         | potentially lost, causing potential crashes in the caller when
         | it maybe shouldn't.
         | 
         | > Lastly is that we would need to change some of the ownership
         | properties that ractor is currently based on to pass an owned
         | self in each call, returning a Self reference which seems
         | clunky in this context.
        
       | helge9210 wrote:
       | Seeing "channel" and "actors can inter-communicate, via remote
       | procedure calls" do I understand this correctly this is not an
       | actor model implementation?
        
       | slau wrote:
       | I'm not saying that you can't use another project's name, but
       | there already exists an actor framework named Ractor, but this
       | one written in Ruby.
       | 
       | https://docs.ruby-lang.org/en/master/Ractor.html
        
       | samsartor wrote:
       | Actors in Rust still kind of suck because there are no first-
       | class resume arguments. In order to provide a `&mut State` you
       | have to either:
       | 
       | - handle messages sequentially per-actor (like Ractor, and like
       | Bastion in practice)
       | 
       | - create your own future trait without async/await (like Actix)
       | 
       | I keep hoping the language team will get coroutines off the
       | ground, but it hasn't happened yet.
        
       | webkike wrote:
       | I personally find actors in rust to be too much of a head ache.
       | You might as well use Erlang (or elixir) and do your heaving
       | lifting in rust with somethign like
       | https://github.com/rusterlium/rustler
        
       ___________________________________________________________________
       (page generated 2024-11-03 23:01 UTC)