Hacker News new | ask | show | jobs
by pron 36 days ago
> But that doesn't mean any language that allows you to implement reference counting as a library, is a garbage-collected language.

The concept of "a garbage-collected language" is not well-defined. There are languages, like Java, Rust, and Python that depend on a garbage collection mechanism, and languages like C, C++ and Zig, which don't. C++ happens to offer a GC in its standard library, however.

That "working developers" use some other terminology is not what matters. What matters is whether the terminology they're using expresses important distinctions or not (and may, in fact, express misconceptions about distinctions). In the case of memory management (as in the case of "transpiles", although there the damage isn't as high), the colloquial terminology is misleading as it is used to hint at distinctions (such as about performance) which are simply not there. E.g. moving GCs are used to avoid the performance overheads of malloc/free, especially in large and/or concurrent programs. This performance overhead that C and C++ suffer from is well known to experienced low-level developers (which is partly why large programs that benefit from moving collectors are relatively rarely written in such languages anymore), but now the terminology is used as a cargo cult, which leads to conclusions that are sometimes the very opposite of what's really going on.

2 comments

> The concept of "a garbage-collected language" is not well-defined. There are languages, like Java, Rust, and Python that depend on a garbage collection mechanism, and languages like C, C++ and Zig, which don't. C++ happens to offer a GC in its standard library, however.

How does Rust differ from C++ in this regard?

As far as I know, both use RAII, and offer something RC-like in their stdlib that is optional to use.

If we view Rust (including unsafe) as a memory-unsafe language, then it's the same as C++, since we can then view Rc/Arc as optional. But if we want to look at Rust as a memory-safe language, then it mandates the use of GC when an object may have multiple owners. In other words, Rust depends on GC to ensure the memory safety of common functionality. It is true that Java depends on GC for even more operations, but the fact remains that it's very hard to write many large Rust programs without the use of the GC in its runtime (unless you go unsafe, in which case it's like C++, where the GC is optional).

I think many people, especially those with insufficient experience with both low-level languages and modern garbage collectors incorrectly assume that the presence or reliance on GC necessarily implies some performance overhead. In actuality, some GCs (moving GCs in particular) were invented, among other reasons, to reduce the overhead imposed by malloc/free that causes significant performance problems in large programs written in low-level languages. Of course, refcounting GCs, as well as some tracing GCs (non-moving ones) also rely on malloc/free, so they may still suffer from the same issues.

Another misconception is that "a GC" is some necessarily large and sophisticated runtime mechanism compared to "no GC". The problem with that view is that modern malloc/free are also large and elaborate runtime mechanisms (in the range of 10KLOC), and they're elaborate because clever sophistication, as well as CPU/footprint tradeoffs, are required to get decent performance from such allocators (another fact that experienced low-level programmers know). Modern malloc/free allocators may be larger and more complex than simple moving collectors (although it is true that modern moving collectors are larger and more complex than modern malloc/free allocators, but they both require non-trivial runtimes).

I think it would be a salutary experience for every C/C++ programmer to write a decently-performing allocator so they could appreciate the complexity and overhead required to avoid excessive fragmentation (especially in long-running programs with long-lived allocations and irregular deallocations), given the constraint of address stability.