> adding a generational counter (maybe only in debug builds) to allocations can catch use-after-frees.
At the cost of making the use of the resulting heap significantly slower and larger than if you just wrote the thing in Java to begin with, though! The resulting instrumentation is likely to be isomorphic to GC's latency excursions, even.
This is the biggest issue that bugs me about Rust. It starts from a marketing position of "Safety With No Compromises" on runtime metrics like performance or whatever, then when things get hairy it's always "Well, here's a very reasonable compromise". We know how to compromise! The world is filled with very reasonably compromised memory-safe runtimes that are excellent choices for your new system.
> At the cost of making the use of the resulting heap significantly slower and larger than if you just wrote the thing in Java to begin with, though!
Lower throughput, probably. But it introduces constant latency. It has some advantages over doing it in Java:
* You're never going to get latency spikes by adding a counter to each allocation slot.
* If you really want to, you can disable them in release builds and still not give up memory-safety, although you might get logical use-after-frees.
* You don't need to use such "compromises" for literally everything, just where it's needed.
> It starts from a marketing position of "Safety With No Compromises"
I haven't seen that marketing, but if it exists, sure, it's misleading. Yes, you have to compromise. But in my opinion, the compromises that Rust lets you make are meaningfully different from the compromises in other mainstream languages. Sometimes better, sometimes worse. Probably worse for most applications than a GC language, tbh.
> * You're never going to get latency spikes by adding a counter to each allocation slot.
The suggestion wasn't just the counter though. A counter by itself does nothing. At some point you need to iterate[1] through your set to identify[2] the unreferenced[3] blocks. And that has to be done with some kind of locking vs. the unrestricted contexts elsewhere trying to do their own allocation work. And that has costs.
Bottom line is that the response was isomorphic to "That's OK, you can work around it by writing a garbage collector". And... yeah. We have that, and it's better than this nonsense.
No, sorry, in case I wasn't clear, I was talking about manual deallocation. I wasn't talking about a garbage collector. You still allocate and free cells. Here's an example of what I am talking about:
>It starts from a marketing position of "Safety With No Compromises" on runtime metrics like performance or whatever, then when things get hairy it's always "Well, here's a very reasonable compromise".
Well, yes. The other compromise is the one Java gives you: write a state of the art garbage collector and include it in your program.
This complaint is very annoying because it assumes a garbage collector is "free" and Rust decided to just not give you one. If you want memory safe trees your options are
* A slow, simple, reference counted garbage collector
* A fast, complex, garbage collector
both are compromises! You are just ignoring the second one.
Well, in some sense. But in practice, come on: "write your own slow simple reference-counted GC" is a rather more expensive compromise than "just use Java or Go or whatever, it's faster and simpler for your problem".
Aside from all the nitpickery about runtime implementation, the rustacean community has a serious problem with compromise in general. If you whine in a python/Go/Java/whatever forum about performance, they'll point you to their FFI and show you how to get what you want via C++ integration, because clearly no environment is going to be perfect for everyone. But come at rust with a use case (cyclic data structures here) for which it's a poor fit and everyone goes blue in the face explaining how it's not really a problem. It's exhausting.
> But come at rust with a use case (cyclic data structures here) for which it's a poor fit
I'm not even disagreeing in general that Java is likely to be better for most problems. But the options in Rust for cyclic data are actually fine:
* You can use integer indices into an array instead of pointers.
* You can use unsafe and raw pointers, and be in the exact same situation as C++. The doubly linked list in the standard library is a cyclic data structure does that.[1]
* You can use reference counting (std::rc)[0]. This is literally a garbage collector in the standard library, equivalent to C++'s std::shared_ptr.
These are all simple. None of them is as complicated as "write your own GC".
> It starts from a marketing position of "Safety With No Compromises"
Since such a tradeoff is not possible (full safety for free?!) I doubt there's any such marketing.
> The world is filled with very reasonably compromised memory-safe runtimes that are excellent choices for your new system.
Bringing a whole managed runtime just to handle a single structure with cycles in your program is not reasonable.
There's no problem with using a simple GC for a tiny part of an otherwise manually managed program just how there's no issue in managing memory manually for a small, but performance sensitive part of your GC managed program.
At the cost of making the use of the resulting heap significantly slower and larger than if you just wrote the thing in Java to begin with, though! The resulting instrumentation is likely to be isomorphic to GC's latency excursions, even.
This is the biggest issue that bugs me about Rust. It starts from a marketing position of "Safety With No Compromises" on runtime metrics like performance or whatever, then when things get hairy it's always "Well, here's a very reasonable compromise". We know how to compromise! The world is filled with very reasonably compromised memory-safe runtimes that are excellent choices for your new system.