11 ms·
Elixir and Rust is a good mix
- brightball 3y agoThis is essentially what has gotten me into Rust. Knowing how easy Elixir makes it to offload a piece of my system that would benefit from it without needing to write the entire thing in Rust.
- peregrine 3y agoAuthor here. The rustler team has really made a great developer experience using Rust in Elixir and I just had to share.
- pawelduda 3y agoI used this in past, but there were a lot of people saying that NIFs/ports are dangerous to use. Did any of this change or are there tried and tested practices that can be applied? What I used this for wasn't critical app so I didn't mind it going down. IIRC one threat was Rust sharing memory with BEAM which could exhaust it and cause OOM crash?
- peregrine 3y agoNif's are always a risk to deploy cause they run embedded in the Beam runtime. That said we all use many nif's to do all kinds of stuff and its fine. I go into this in the article a little but but the ruslter team has made dirtycpu and dirtyio macro's to help reduce the risks.
- toast0 3y agoI wouldn't use the word dangerous. NIFs and (linked in) ports are a risk, yes. You're loading native code, and you don't have intrinsic protection against it doing naughty things that alter the underlying shared state in unapproved ways. Things like writing to improper addresses, closing improper filehandles, opening filehandles and leaking them, leaking memory, other improper use of syscalls, or just running for too long. The only proper solution is to audit and understand the code you're running, but hoping it's fine often works too; maybe formal methods, but to a first approximation, nobody uses those. Did you audit all of ERTS and/or OTP? I'm guessing probably not, but it's there to review if you run into a problem. IMHO, it's not worrying about if BEAM will crash; worry about it not crashing instead. If your Rust NIF ties up a scheduler with an infinite loop, that has the potentially to lock up the whole BEAM once another scheduler needs to do something that requires full cross scheduler coordination. BEAM can certainly crash on OOM; although I recommend setting a ulimit to ensure it will, because when it crashes, you can recover. I've also run into situations where instead of crashing or being killed by the OS OOM killer (which is close enough to crashing), the OS gets into some tricky to debug state where your application is neither functioning nor killed. Sometimes, you even get into a state where BEAM is making progress, but very slowly; that's a fate worse than death. If you follow the Erlang philosophy, you'll have a recovery strategy from crashes or other deaths. Heart can be used to turn completely blocked into death, although I never used it professionally. But you've still got to worry about working but not well.
- OkayPhysicist 3y agoNIFs are dangerous, ports are not. Basically a NIF runs in the same memory space as the BEAM, so misbehavior of the NIF can crash the entire application. On the other hand, they have wildly better performance than ports, which have to operate through STDI/O. The Rust / BEAM memory sharing problem does exist, but it's not nearly as bad as in more traditional C NIFs, because almost all C programs leak memory due to bad manual memory management. Hence all the buzz about Elixir+Rust.
- throwawaymaths 3y agoports can be dangerous! One time my server lost performance because of a lot of zombie processes.
- toast0 3y agoI personally include port drivers in with ports, which gives you the same level of shared runtime environment as NIFs, with the benefits and drawbacks. Sometimes it makes more sense to interface as a port rather than as a function. Of course, sometimes it makes more sense to interface as a C-node rather than either; then you can be on a totally isolated machine, and the C-node can even crash or otherwise disable the whole OS and your Erlang node will be fine.
- OkayPhysicist 3y agoWhat's the benefit of a port driver over a NIF?
- toast0 3y agoA port driver can participate in the BEAM event loop, adding filehandles that get called back when they're ready. It's a more appropriate choice for something that's asynchronous, although NIFs do have ways to fill the same role. A port driver would probably be a better choice for specalty networking that ERTS doesn't provide (raw packets? netgraph, etc).
- waynesonfire 3y agoIO in erlang is slow, is Rustler a good candidate to improve IO intensive applications?
- throwawaymaths 3y agoWhere did you get this idea? Depending on your task, Erlang can be very fast with IO because it will aggressively use writev/readv instead of write. Obviously, you can do this if you "know about it" in low level langs, but it might be a pain. Erlang is generally considered to be compute-slow (which is generally the case without dropping to nifs).
- toast0 3y agoIs IO in Erlang slow? I never got that impression; iolists are a good fit for scatter/gather I/O, which reduces copying, and Erlang supports kqueue, epoll, and I believe similar on Windows. I don't follow Erlang on Linux closely, so I don't know if there's any movement towards io_uring, which does seem promising to help with throughput. Either way, I wouldn't expect doing I/O in NIFs or ports to substantially improve throughput, unless you're also moving significant processing into that layer as well, or you're going to end up doing substantially the same level of marshaling work as ERTS does, just in a different language. Setting up a different set of kqueue/epoll descriptors sounds like a lot of work for not much gain too, IMHO; again, maybe io_uring would be useful, but I think you'd be better served to bite the bullet and integrate it with ERTS.
- weatherlight 3y agoI/O in beam languages is considered pretty fast, are you sure you aren't mistaken?
- sph 3y agoIO in Erlang is faster than many other managed languages, thanks to stuff like IO Lists that are dealt with very efficiently through iovec syscalls (readv, writev). Then add efficient pattern matching for binary data, and networked servers on the BEAM are the most ergonomic than any other language. All this stuff that the BEAM offers you out of the box can be replicated in any native language with a lot of boilerplate and ceremony. Your "Hello, <name>" webapp in Rust will probably need two allocations and a string concatenation, while on the BEAM, if constructed as an iolist, it's a single writev syscall, using a static "Hello, " string and a shared "<name>" reference from the parsed HTTP data.
- satvikpendem 3y agoI used to use Elixir, but the lack of static types got to me (especially since I prefer the type-driven development methodology). Using Rust afterwards was great, plus it was faster than the BEAM. I guess, why not use Rust entirely instead of as a FFI into Elixir or other backend language? I've been using Axum and it works pretty well. The only time I had to do FFI with Rust was with Flutter via flutter_rust_bridge, for running a CRDT library (automerge) that was implemented in Rust, for offline app functionality.
- hosh 3y agoEveryone have different needs. For us, OTP on top of pre-emptive lightweight threads and a REPL in production is a huge winner for troubleshooting, finding performance bottlenecks, and reliability.
- malkosta 3y ago> why not use Rust entirely instead of as a FFI into Elixir Because many times you value fault-tolerance and distribution more than performance.
- rapnie 3y agoThere's a couple of Rust libs and frameworks inspired on Erlang/OTP in 'best of both worlds' attempts, such as https://lunatic.solutions https://lunatic.solutions I found others like Lunatic before, but cannot remember right now.
- malkosta 3y agoI guarantee your life will be simpler with Erlang.
- eikenberry 3y agoCould also be nice as it means the Rust parts are smaller and so its slow compiler speed isn't as much of a bother. You get the safety and runtime speed of Rust with the fast iteration cycle of Elixir. Best of both worlds type of thing.
- alberth 3y agoDoesn't the use of Rust have to be extremely minimal because ErlangVM has hard time limits on their preemptive scheduler and if your Rust code hasn't finished when preempted, that causes lots of problems. EDIT: thanks for pointing out where in the article this is talked about.
- peregrine 3y agoI go into this in the article :) the rustler team has a made a DirtyNif that can work around that, or you can manually yield if you'd like.
- clessg 3y agoIndeed, your (excellent) article addresses this, here's the gist for those following along: > Change `#[rustler::nif]` to `#[rustler::nif(schedule = "DirtyCpu")]` > This tells the Rustler and BEAM to automagically schedule this in a way that won't block the entire world while it works. Again amazing, this is called a DirtyNif and is way more difficult to work with when you are manually using this via C. Essentially, regular NIFs have to be extremely fast (< 1ms) because the VM can't preempt them - they run on the same scheduler threads the BEAM itself uses. Dirty NIFs solve this by running jobs in a completely separate thread pool ("dirty schedulers"). Rustler's docs explain it succinctly (https://docs.rs/rustler/latest/rustler/attr.nif.html https://docs.rs/rustler/latest/rustler/attr.nif.html): > For functions that may take some time to return - let’s say more than 1 millisecond - it is recommended to use the `schedule` flag. This tells the BEAM to allocate that NIF call to a special scheduler. These special schedulers are called “dirty” schedulers. > We can have two types of “lengthy work” functions: those that are CPU intensive and those that are IO intensive. They should be flagged with “DirtyCpu” and “DirtyIo”, respectively. (Somewhat OT, but since I'm here: excellent article @ peregrine! I really enjoyed the read. Elixir and Rust are such a perfect fit. Plus, some of the specifics will be helpful for certain image-related things I'm actively working on, which is always nice. :) )
- maciejgryka 3y agoShameless plug & also a point of support for the article: I used that same stack to build https://regex.help/ https://regex.help/ (more details here https://maciej.gryka.net/building-regex-help https://maciej.gryka.net/building-regex-help)
- freedomben 3y agoNeat! You should definitely add a link to the github repo to the main page of regex.help though, because I didn't realize it was open source and I'll use it now. Github link for others: https://github.com/maciejgryka/regex_help https://github.com/maciejgryka/regex_help
- Dowwie 3y agoIf you're extending Elixir with Rust, familiarize yourself with precompiled rustler: https://dashbit.co/blog/rustler-precompiled https://dashbit.co/blog/rustler-precompiled
- bglusman 3y agoI admit for a long time this was my primary motivation to learn Rust, but, sadly, I haven't come across problems in years that were CPU bound/where I needed something like Rust... Rustler still looks like a great fit if needed, but, depending on the use case, if I were CPU bound and needed to write my own code/not just use a Rust library, I'd be as or more likely to look at using Zig and Zigler[0], for much faster learning curve, and from what I've read, easier tighter integration into elixir, including I think language server integration. Some discussion here[1] though I forget if I listened to this one or not. [0]https://github.com/ityonemo/zigler https://github.com/ityonemo/zigler [1]https://podcast.thinkingelixir.com/83 https://podcast.thinkingelixir.com/83
- packetlost 3y agoEfficient PLs are useful for latency sensitive applications too, not just CPU bound ones. Though, that doesn't widen the number of cases by much...
- jimbob45 3y agoI haven't come across problems in years that were CPU bound/where I needed something like Rust This is where Rust falls short of C#: scaling to the issue at hand. C# can build you a beautiful app at a high-level but also lets you dick with pointers and assembly at a low level. Rust insists on defaulting to pass-by-move and an arcane trait system that hold it back from being usable in large projects.
- zelphirkalt 3y agoIf Rust had gone for a traditional OOP system, the "everything must be OOP/use inheritance everywhere" crew would have messed up the ecosystem pretty quickly. The traits concept is refreshing and traits + structs encourage composition over inheritance. I think it has been a huge plus for the language and the ecosystem.
- kaba0 3y agoI really like the trait system, but “refreshing” might not be the correct word given that it is pretty much what Haskell had for I don’t even know how many years.
- FpUser 3y agoI remember MS reps coming to our offices in the 90s and "Teaching" us how to program and how it is cool to have one person writing GUI and the other "guru" writing high performance C++ to be used in critical parts. I showed them a piece of software that was mighty fast, Internet enabled and GUI intensive. They liked the software but asked where did you get this particular screen control from. You've got to see their faces when told that the whole software was written by a single person in Delphi.
- mrmincent 3y agoI made the ‘risky’ decision to learn Elixir & Phoenix for a 6 week University project that I delivered earlier this week. It could have turned out to be a terrible decision but honestly once I got my head around it, it’s probably some of the most productive coding sessions I’ve ever had. Deploying to Fly.io was also amazing, I think it was 3-5 commands from signup to deployed with a database. Very happy with it all.
- MuffinFlavored 3y agoterraform init && terraform apply is 2 commands! slightly /s kubectl apply is 1 command! ansible-playbook is 1 command! sh ./do-the-thing.sh you get it
- benatkin 3y agoThis makes me think of DotCloud, the precursor to Docker Inc. The idea that you can run anything has been around for a while. I don't see how Fly.io is especially good for Elixir. It seems to be about delivering reliable resources for each service, and it works no matter the web framework, as long as you have a PaaS that's geared towards running arbitrary OCI images. These posts seem a lot like the content on the DigitalOcean site, except they're written by staffers instead of freelancers. https://www.digitalocean.com/community/pages/write-for-digitalocean https://www.digitalocean.com/community/pages/write-for-digit...
- mrdoops 3y agoElixir does distribution and concurrency really well, so Fly making multi-region deployments easy makes it a good fit.
- benatkin 3y agoSo is Fly good for small sites or is it good for huge sites? What's a big customer? For small sites my idea of a multi-region deployment is a single-region deployment that works in multiple regions thanks to the magic of the Internet. I see this which mostly seems to be content sites. https://www.wappalyzer.com/technologies/paas/fly-io/ https://www.wappalyzer.com/technologies/paas/fly-io/ Same on the first forum result: https://community.fly.io/t/customer-success-stories/4882 https://community.fly.io/t/customer-success-stories/4882
- mrdoops 3y agoI'd say its good for both. Small sites because it is low-bandwidth to figure out how to use. Time is money and if I'm just tinkering on the weekend I don't really want to learn Kubernetes or the labyrinth that is AWS; I just want to ship an app. Big sites because your users get routed to the node closest to them and again you can do this without a lot of time investment. For Elixir I just wire up Libcluster and my nodes can talk to each other. I really want GPUs on Fly soon though. Just take my money Kurt. (I hear they're working on it)
- 3y ago
- mtremsal 3y agoThis is amazing! The Gods have sent me the perfect excuse to finally read all the rust ebooks I’ve been collecting! I have a toy project where certain functions are CPU-bound and would make for a perfect learning project. Very timely read!
- victorbjorklund 3y agoElixir, rust and fly.io in the same post. HN bingo.