Surely oxidation is better than carcinization. And rust evolving to Java would be mineralization? Or maybe germination?
Makes more sense to me that a language/framework/etc. would 'oxidise' to Rust, and someone learning/getting hooked on Rust would 'carcinise'.
But this is all silly and doesn't matter anyway, ha!
If you have values A and B, both instances of T that implement Drop and hold a Gc<T> to one another, then either A sees a dropped B or B sees a dropped A, and you get UB. Technically this isn't a problem because you already marked it as an unsafe trait, but your documentation should also mention this problem.
You have a safe derive macro for Collectable, however, so it needs to reject Drop. This is possible with some weird macro magic[0].
For those wondering, while Python doesn't have UB, it used to enforce the same rules until PEP 442[1]. Circularly linked garbage that implemented __del__ would instead get stored in sys.gc.garbage and you'd have to go in there and break references on __del__ types. However, native code objects will actually still trigger this behavior[2] presumably to avoid UB.
I have no clue if Java finalizers need to worry about this.
[0] In Ruffle we use gc_arena as our garbage collector. It enforces no_drop in it's Collect derive macro. See: https://github.com/kyren/gc-arena/blob/master/src/gc-arena-d...
Actually, I lied: no_drop is one of two safe constraints you can use Collect with. The alternative is require_static, which only works because gc_arena treats the entire GC heap as a data structure that is owned and has a lifetime that all Gc pointers borrow. This doesn't work for dumpster, though, so ignore it.
If you look at the Hylo (formerly Val as of 3 days ago) language [0] that folks like Dave Abrahams have been working on, they're outright saying to just not use reference semantics if you can avoid it. In this talk at CppCon about Value Semantics, Dave argues that we should be decoupling object graphs from access to objects [1]. That's right folks, using `usize` indexes into collections, or adjacency lists, isn't just a hack to get around the borrow checker as people like Jonathan Blow have famously critiqued [2], it may just be the right way of doing things.
I know there's a nontrivial overhead to using index-based references, especially since CPUs can't easily predict load operations. This gives me an idea: create a CPU architecture where pointers are actually (object, offset) tuples, enabling better on-metal performance with index-based references. I've neither the time, money, expertise, nor energy to implement such a thing, but it would be really cool if it existed - maybe CHERI is a close approximation, though I haven't looked very closely at it.
I dunno, isn't a usize index into a collection essentially just the same as a pointer? Ok it's a bit safer because you at least know the type of the object you're accessing is correct, but you can still get e.g. use after free bugs.
I think Jonathan Blow is right and wrong - it is a hack to get around the borrow checker, but also that's totally fine. The borrow checker still works for the other 95% of code you are writing. Nobody ever claimed it was the perfect solution to every problem.
Heap integrity / reachability is a dynamic property of a running program, and so all the static solutions basically end up as half-measures. (I imagine this would include using macros, though I don't know exactly what you mean),
FWIW this is my experience - https://www.oilshell.org/blog/2023/01/garbage-collector.html
Take it with however many grains of salt, but I heard many static solutions proposed, and they're all "wrong" for the simple reasons in theory of computation.
Also, Rust's static memory management inherently clashes a bit with dynamic memory management. That's also a fundamental thing, and you can have a bazillion mechanisms to ameloriate it for some cases (which may be valuable), but the problem will still be there no matter what.
Rust already has precise heap allocations with Box and Rc/Arc without the need for GC. GC implies uncomputed heap allocation liveness deferring work until later.
this is nice
> requisite time trudging through the lifetime swamp
Rust really isn't like that. It gets a bad rap.
> I couldn't couldn't imagine running my whole life on the stack
Most of the things you touch in Rust are heap allocated.
> this would be a really nice language if it were garbage collected
It is a nice language, and all of your complaints go away once you start using the language idiomatically.
Oh you can absolutely say that.
It's just a dumb take. Because there are already languages which have a solid type system, a functional bent, and a GC. And you can probably bang out one yourself if you want.
If that was what Rust was, it probably wouldn't have existed in the first place, Mozilla would not have been interested in it in the second place, and no community would have gelled around it in the third place. Hell, it was specifically dragged further downstack by the people who coalesced around its potential in that space.
Is it just me or do release semantics not make sense for a load? Release is for stores (I'm coming from the C/C++ atomics model). Hm, the docs[1] say a Release load will panic:
> Panics if order is Release or AcqRel.
Maybe just a blog transcription typo for tag.store(TAG.load(Relaxed), Release).
[1]: https://doc.rust-lang.org/std/sync/atomic/struct.AtomicUsize...
It is in general undecidable whether an object is unreachable, but we can get good performance with heuristics.
Edit: Well, I didn't think it through. You're correct. A "perfect GC" is indeed undecidable, and all the real world, practical GC have "false positive" (a piece of memory is no longer used by the code, but there is no way to know it). A perfect GC without false positive is equal to halting problem.
Example:
var obj = new Object();
// === real code begin
... 50 lines of real code that never uses obj
if (something == true) return;
... another 50 lines of real code that never uses obj
// === real code end
obj.doThings();
A perfect GC can release obj while the real code is running. A real world, imperfect GC can't, since whether the real code returns early or not is undecidable.The conservative approximation that GC's make is that they keep all objects that could be accessed in the future by the program (reach-able) instead of only keeping the objects that will be used in the future.
A trivial example is:
void main() {
var obj = new Object();
while (true) {
// Do other work...
if (dayOfWeek == 9) {
print(obj);
}
}
}
Since there are only seven days in the week, that `print()` statement will never be reached, and an optimal GC would free obj. But the GC can't determine that. All it knows is that it could possibly be reached, so the object stays in memory.1. The GC frees objects that could be reachable (program gets corrupted in spectacular ways).
2. The GC never frees anything (as it can't decide).
The GC may not dispose of objects immediately, but when it determines they're not reachable, they're not reachable, period. There's nothing undecidable about it.
The issue is not necessarily unreachability being undecidable. The issue is that reachability depends on control flow. And that implies being conservative which translates into having to deal with false positives.
That static analysis in a nutshell: how to get precision and soundness.
Probably a mere question of terminology.
https://web.archive.org/web/20130607161259/http://pcwalton.g...
It will be very interesting to survey how many Rust implementations nowadays do away with memory safety [1].
Not sure if the author of the article referred to this seminal paper on Rust for GC for his GC implementation for Rust in Rust [2].
[1] What is a safe programming language?
https://cs.stackexchange.com/questions/93798/what-is-a-safe-...
[2] Rust as a Language for High Performance GC Implementation:
https://users.cecs.anu.edu.au/~steveb/pubs/papers/rust-ismm-...
Rust is facing a similar problem in terms of sync vs async, which seems to limit options primarily for folks who want to avoid async, but it doesn't seem like a major blocker for adoption comparatively.
Overwhelming majority of mainstream programmers want important choices to be made for them.
- People want language designers to decide: Has GC or no GC. Choose one and be consistent with it. Then all the libraries will be written on top of it.
- People want language designers to decide: Has async green thread or not. Choose one and be consistent with it. Then all the libraries will be written on top of it.
- People want language designers to decide an auto format syntax style. Choose one and be consistent with it. Then all the libraries will be written on top of it.
etc. etc.
Rust takes its fundamentals from more advanced and well thought out functional languages. As a result it outclasses D and its ilk.
I actually haven't read that paper! Thanks for sharing it. What I'm doing is slightly different - it looks like they're building a conservative GC with an unsafe API, much like one might for C. My intent was closer toward building a GC which can be used with any safe Rust code more or less unconditionally.
So if I understand correctly, every time a Gc drops you add it a hashmap and then periodically, you run through all Gc's in that map and trace all their children to see if they are part of a cycle? I still don't understand how this would work without knowing the rootset. Just because something is part of a cycle doesn't tell you if it is inaccessible. There must be something here that I am missing.