3 ms·
While people spend much more time arguing matters of taste and trading urban legends about performance back and forth when discussing garbage collection, finali
by jerf 3y ago
While people spend much more time arguing matters of taste and trading urban legends about performance back and forth when discussing garbage collection, finalizers may be the most potent issue that garbage collection raises, at least from an overall correctness perspective. As near as I can tell, it has proved an intractable problem to get perfectly correct finalization with garbage collection. Many, many implementations have tried it, and all the ones I've ever seen have either ended up with a laundry list of warnings around them along with a warning that the list of warning itself may still be incomplete, or they pull them out entirely.
It may be worth reading that previous sentence again. I've seen it so many times. Even in the comments on the linked article I see people trying to propose solutions to the problem. But it turns out to be one of those problems that looks simple, even super simple ("just" provide a function to do it), but then it turns out that hundreds of smart people have poured years into solving it... and failed, as far as I know, universally.
Fortunately, you need to have a very particular kind of program to get hit hard by the problem, and an even more particular kind of program to not have the ability to mitigate it into an acceptable issue somehow, most often something like being more careful to close files when done with them. I think I brushed the issue once and it was addressed by exactly that, being more careful to close files when done with them because we accidentally were leaning on GC to close them and that become a de facto file handle leak.
That said, the problem may be exposed by GC but it isn't entirely caused by it. Sufficiently complicated manually-managed memory programs can encounter things that stem from the same underlying issue. If the lifetime of objects that need a "finalizer" (or destructor, if you prefer) becomes sufficiently complicated, it can also become very difficult to figure out when they need to be cleaned up. But you get more runway, and you have more options to deal with it when you have the ability to more forcibly descope things and deallocate them at a known point in time. When I was programming with libpurple, the generalized IM library written in C, it used a lot of "closures" (implemented in C), and figuring out when to correctly deallocate and finalize on them was very, very difficult, because their lifetimes were complicated and not terribly well documented. This is the same underlying problem in a lot of ways, it just manifests differently. But in a manually-managed world, it is at least a solvable problem, however hard the problem may be to solve in some languages (while they had no better option at the time, I gotta say in hindsight libpurple would really have been served by Rust, and I'm not thinking the generalized "rewrite everything in Rust" here but specifically that all those closures would have been much safer to work with with lifetime annotations enforced by the compiler), and however difficult you may make the problem on yourself with your own programming practices.
Alas, no silver bullet.
- deleted 3y ago[deleted]
- epcoa 3y agoIndeed, it isn’t caused by it at all. struct S { int s; }; int *i; { struct S s; i = &s.s; } *i = 7; // and in C++ you can even write int& j = *i; No GC or finalizers involved here. Hope you have ubsan. The point being that GC is sort of a red herring in the entire discussion. Easily subverted lifetime and ownership semantics are going to be error prone irrespective.