For example: I wonder if it wouldn't be more "erlangy"/"elixiry" to model the mutable ops behind a genserver that you send messages to. In the Elixir world it's perfectly normal to make GenServer.call/3 and expect the target PID to change its internal state in a non-deterministic way. It's one of the only APIs that explicitly blesses this. The ETS API is another.
Alternatively, you could have the ref store both a DB sequence and a ref ID (set to the last DB sequence), and compare them on operations. If you call FeGraph.set/2 with the same db ref two times, you compare the ref ID to the sequence and panic if they aren't equal. They always need to operate with the latest ref. Then at last the local semantics are maintained.
Maybe this is less relevant for the FeGraph example, since Elixir libs dealing with data are more willing to treat the DB as a mutable thing (ETS, Digraph). But the it's not universal. Postgrex, for example, follows the DB-as-PID convention. Defaulting to an Elixiry pattern by default for Rustler implementation is probably a good practice.
The real code that this is based on is in fact hidden behind a GenServer for this exact reason -- to maintain the expectations of other Elixir code that has to interact with it. The advantage of the escape hatch, as another commenter mentions, is allowing efficient sparse mutations of a large chunk of data, without having to pay a copy penalty every time. I definitely wouldn't recommend sharing the db handle widely.
When you're presenting a GenServer like message passing interface a port is a natural fit, with none of the risks related to linking a NIF into the VM itself.
(admittedly those risks are much lower with Rust than C)
It depends on the use case. For example, when creating a resource (basically a refcounted datastructure), it might make sense to allow mutable access only through a process as the "owner" of the resource. But if you have only read-only data behind that resource, sharing the resource similar to ETS might be what you want.
Integrating Elixir and Rust has been delightfully straightforward and is a great choice for calling into libraries not available in Elixir, or offloading CPU intensive tasks.
[0]: https://www.doctave.com/blog/2021/08/19/using-rust-with-elix...
We had some inconsistent build results (ours is an umbrella app) but apart from forcing a compilation and losing the ability to cache the rust builds, everything else has worked so well so we’re happy to get access to the massive rust ecosystem.
Unfortunately, I haven’t had a project where I’ve needed to use Rustler yet, though.
So using C and Zig libraries without fully understanding them can be a death trap while in Rust as long as it doesn't use unsafe code you can feel pretty good about using it.
It's entirely the rustler project's effort (and goal) to wrap any kind of Rust program so that it will not bring down the BEAM under any circumstance, which they have done a great job achieving.
I don't normally see people consider (D)ETS tables as mutable, however.
It may not be the only way to get to scalability and robustness. But it certainly is the cornerstone of how Erlang gets there.
1. First, the way Erlang treats data ensures that every piece of data can be sent over the wire by default. This helps pave the way for another amazing characteristic of Erlang, and that is when you refer to and use an object, it's essentially transparent to your code whether that object is on this machine or another machine in the cluster. This would not be possible without the fact that all data structures are remotable, which is enabled by the immutable data. (See also side note below.)
2. The immutable data also leads to clean rollback semantics, making it easy to always have a self-consistent state of the system ready to use even after some kind of fault.
3. The immutable data also leads to very clean and easy ways to handle multithreading because you never have to worry about making object copies. You can be assured that it's ok for two threads to use the same memory object because there's no way either of them can change it.
Side note: Alan Kay, the inventory of OO, has said that people get the entire idea of what he was talking about all wrong. He said that object orientation isn't about objects, but its about communication. He was talking about the idea of an object being more like what we'd call a web endpoint today, where when you instantiate it you communicate with it by sending it messages. It's funny to me that a functional language like Erlang best embodies that OO idea today. Go code can, too.
"I'm sorry that I long ago coined the term 'objects' for this topic because it gets many people to focus on the lesser idea. The big idea is 'messaging'" - Alan Kay <https://en.wikipedia.org/wiki/Alan_Kay>
He goes on in the original underlying document to say "OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things." All of these ideas are front-and-center in Erlang (and by extension Elixir).
Does this mean a web-endpoint has to be immutable? If you send it the same parameters multiple times, is it required to respond with the same response every time? If not, does that not mean it is in fact mutable?
I read elsewhere that in Elixir programs, there is no difference in messaging a local "agent" or a remote one? The caller does not know whether the other party is remote or not. Is it still guaranteed to be immutable?
Just asking since I don't know much about Elixir.
https://discord.com/blog/using-rust-to-scale-elixir-for-11-m...
Personally I think that if you can stomach the additional complexity (which is a non-trivial "if", but a doable one), Rust's approach supercedes immutability. Full immutability was an interesting theory in the 1990s, and I mean that respectfully and not sarcastically, but in the end I think it was overkill and overcompensation. The correct thing to do is not to eliminate mutability, but to firmly, firmly control it. Rust has a sophisticated method for doing so, with compiler support. It may not be the only one, but it seems a very solid one. Immutability is another method of controlling it, but in my view, it's actually kind of a blunt instrument applied to a complex problem.
In my considered opinion, in the end, immutability isn't even important to Erlang. What matters in Erlang is that you can't send references across messages, so there is no way to witness mutation done by another process. It was not necessary within a given process to be immutable, and I suspect that has been a non-trivial ball and chain around Erlang's legs in terms of adoption even to this day. There was never any need for a newbie Erlang developer to also have to learn how to program immutably within a process.
Immutability is a tool, not a rule, and I am free to reject any assertion otherwise when those assertions provide no evidence, or shitty anecdotes.
Prove your claims.
Certainly, immutability is a foundation for performance problems.
Another provable rule in computing is that more lines of code = more bugs. Immutability uses more lines of code.
Another demonstrable fact is that Haskell based programs have just as many bugs as any other programming language whether you have immutability or not. Therefore, immutability is not a bastion of robustness.
You’re going to have significant difficulty proving to me that immutability = scalability and robustness when both are demonstrably not true just by taking measurements of thing you expect to improve out of those foundations.
Immutability is not a silver bullet. It is a tool that is sometimes useful, but has significant drawbacks, including shitty performance, and significantly limiting how your data can be managed (without that limitation paying off in any significant way)