[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)