I don't entirely agree, I think having a garbage collected pointer (GCP) can make a lot of sense as a performance optimisation: manual memory management (MMM) and reference counting (RC) have stampeding characteristics similar to GC pauses when releasing large hierarchies e.g. persistent data structures, or large trees of widgets: all the tree is freed synchronously and recursively, if it's large it can be very sensible.
This stampeding is more predictable than a GC pause, but it's no less problematic when it occurs, and mitigating it can be difficult (you have to manually shunt the objects you'd like to release off-thread). A GCP can do that shunting on its own, letting a GC thread perform the releases asynchronously, and piecemeal.
Furthermore, GCP means you're not expecting to precisely track allocations at the system level, which means the usual tricks (arenas, bump allocators, ...) can be applied by default to GC pointers, where they usually can't be to MMM or RC pointers. This decreases allocation and deallocation overhead by reducing the amount of work the system has to do.
You could also use async programming to do the same thing in GC-free code. Or just use an arena that can be freed all at once.
No? Allocations are blocking, "async programming" won't do anything.
Unless you have an async dealloc which does the shunting implicitly, at which point you don't need an async dealloc, you can just have a sync one which shunts the actual dealloc and mandate a background thread. Which means now you're mandating a background thread for freeing memory. And you hope that the load is light enough that implicit thread which you've tasked with all deallocations (rather than just the ones which make sense) keeps up.
> Or just use an arena that can be freed all at once.
That is essentially the same thing, you now have a different allocation strategy for these, except it's a lot more limited and specific.
Modern malloc/free implementations must satisfy requirements such as being scalable. It eliminates any chance to implement blocking deallocation from non-owner threads. A modern implementation already has to be async in some ways. You can check paper behind Mimalloc for example.
Your objects in trees and arrays don't have the same lifecycle?
Quoting GP:
> ... reference counting (RC) have stampeding characteristics similar to GC pauses when releasing large hierarchies e.g. persistent data structures, or large trees of widgets: all the tree is freed synchronously and recursively, if it's large it can be very sensible.
This is mostly where RC & MMM fails. This is also where you are supposed to use arena allocators.
If you have a tree you're constantly adding to / deleting from, you can use a Pool backed by an Arena. At that point you don't use pointers, only indices into the pool. When you want to remove something from the tree, you mark the objects in the pool "deleted" until you want something added back again.
No, there is a nested set of lifetimes.
Take a hierarchy of widgets for instance, if you change a sub-view you're going to reclaim the widgets composing that sub-view, replacing them with widgets of an other sub-view.
Then there's persistent datastructures, where lifetimes are reversed (higher nodes are younger), you update a node, you're going to invalidate all the nodes on the path to that, but the nodes which are not on that path are still alive.
> If you have a tree you're constantly adding to / deleting from, you can use a Pool backed by an Arena. At that point you don't use pointers, only indices into the pool. When you want to remove something from the tree, you mark the objects in the pool "deleted" until you want something added back again.
So you're implementing an ad-hoc GC by hand (and so does everyone else) and making your traversal more complicated.
If there's a GC pointer provided (by the language or a library), in the vast majority of cases it will work well enough and you won't need to waste time on that, you can solve actual issues instead.
There are other aspects where D gets hit from both sides because it never picks a side, instead it tries to cater to both sides, never going full into any of them. I guess there is some benefit in a language that's general and doesn't force you into specific paradigms, but it also increases the surface area (maintenance area) of the language and adds complexity for developers when every step of the way you have to consider the alternatives.
Why? The gc is optional in D. You can use malloc/free, or your own custom allocator.