“Convenience” makes it sound like syntax sugar, it’s not merely convenient.
It eliminates certain types of bugs fundamentally, often it can simplify how the code is reasoned about.
Just like static typing is a convenience. And dynamic typing is a (different) convenience. And unit tests are a convenience.
The computer runs fine without any of these. And none of them are a magic wand that eliminates all bugs. Instead all of them make debugging easier by reducing the search space, usually eliminating fairly trivial bugs. Which is more useful than it sounds because a lot (the majority?) of bugs are pretty trivial and at the same time hard to find because they are often so trivial that they stare us in the face and we can't see them.
But again, all these things are conveniences for the human programmer, they don't make one iota of a difference for the actual computer running the bits.
But maybe the view of modern society as a tower built on the back of a million million one time conveniences that have since become indispensible isn't that far from the truth either?
Even if language X loses in microbencharks against language Y, if the application written in X is still within the project delivery SLA for acceptance testing, who cares about the microbencharks.
As a real world example, consider a firmware boot loader I wrote for a previous client. They needed to apply code polymorphism (think, extreme ASLR) when they applied an over-the-air update or a factory reset of firmware. This is part of their defense in depth strategy: if a gadget attack were discovered in their system, they wanted to mitigate its effectiveness across all deployments. The problem with this approach is that boot loaders are tightly space constrained. To implement a sound code polymorphism strategy, around a dozen different heuristics must be applied in several passes over the code. These heuristics had to be weighed against timing and cache coherency constraints in the code under mutation, but most critically, these heuristics had to fit in the very limited space set aside for the boot loader. The GC was an optimization. It significantly reduced the code size of the heuristics.
The last 50 years of CVEs suggests they're not so trivial.
That's why static typing and unit testing are both practically much better at reducing bugs than they have any right to be theoretically.
Java is one of the languages to blame for such misunderstanding, many other languages, even Lisp variants like Interlisp and Common Lisp, provide all the tooling to manage resources like one would do in C and co languages.
Having a GC doesn't preclude those other features.
On D's case, we have the problem that language designers are against moving the GC forward, as they refuse to introduce managed pointers alongside untraced pointers.
So even optimizations that Mesa/Cedar, Modula-3 and Active Oberon were capable of before D came to be, aren't possible in D with its current language design.
This is incorrect. There even was a concurrent collector written for D, but it failed for technical reasons, not anyone blocking it.
The idea that I would block any improved GC implementation for D is just silly. D is free and open source and Boost licensed. I couldn't possibly stop anyone from doing a better one.
You're free to prove me wrong about what can be done, I would welcome your implementation!
C# already offers what I care about in managed languages with C and C++ like capabilities.
And honestly why would anyone go through the trouble to prove you wrong, if in the end it eventually has the same fate as the concurrent one, that only works in Linux anyway.
I'm curious why you don't use Managed C++ (the one with separate GC pointers)?
Managed C++ was replaced by C++/CLI in .NET 2.0.
It is the best way to write bindings to native libraries on the .NET ecosystem without having to bother with getting P/Invoke or COM bindings attributes quite right.
However it is constrained to Windows, so for portable .NET code, P/Invoke it is.
In fact C# actually has three models of pointers, regular references, JIT intrisics like IntPtr/UIntPtr and raw unsafe pointers.
The general idea is pretty straight forward, you have a ~fixed size chunk of (virtual) memory that active memory objects reside in. Garbage and very rarely used references get flushed to disk. Presumably, if the operating memory requirements (i.e. active objects) are met by available Ln cache layer, this should be a viable alternative to 'collecting garbage'. We're trading the overhead of tracing/ref-counting with the cost of cache mechanism. If cache is tiered, then the 'garbage' & 'rarely used' objects will end up in nice VM blocks that OS will flush to disk. One approach basically requires the use of one annotation at language level to distinguish 'long lived' objects.
In the beginning the biggest drawback seemed to be performance, so people have been hell-bent on fixing that for decades now. Completely ignoring that the "don't need to care about memory" mindset is flawed. You always need to think about memory, and if you do you might as well do it yourself. It doesn't take more time to do in the long run and you are forced to design a better system because of it.
For scripts it is a really nice have. But for a proper language writing large-scale applications? No thanks.
Well, it would be a bad argument :D
A reference counter is arguably the slowest option, so Swift is out of the picture (besides it leaks circular dependencies). Rust is a good idea, but it is a tradeoff. Quite a lot of programs can’t be expressed with strictly tree-like lifecycles, and then you are either left with the yet again slower (A)RC, or unsafe. C++ is in pretty similar shoes. They are good for the niche they were made, systems/low-level programming.
Let me quote from the Garbage collector handbook: “above all, memory management is a software engineering issue. Well-designed programs are built from components [..] that are highly cohesive and loosely coupled. [..] modules should not have to know the rules of the memory management game played by other modules. [..] GC uncouples the problem of memory management from interfaces.”
It says nothing about not thinking about memory, you are free to optimize it where you need it.
EDIT: Oh and dynamic allocation is much faster with a modern GC than with malloc and friends, plus a moving GC can defragment heap.
It's not that simple. Obligate refcounting has lower throughput than obligate tracing GC but offsets that with more predictable latency, which might be good for the Swift use case. Rust uses a mix of stack-based allocation, manual heap allocation and RC - and the RC only needs to manage "owners" as determined via the borrowck, as opposed to ephemeral references. So is way more efficient than something like Swift ARC, where refcounting is essentially ubiquitous and any operations via references will incur slow atomic RC updates.
That’s shared pointers recursively freeing up their reference trees. There is a great paper showing that RC tracks object death, while tracing GCs track object “liveness”, so its essentially two sides of the same coin.
As for RC, sure, at compile time many of the work can be omitted, but atomic writes needed for the counters are fundamentally slow operations on modern architectures.
GC will always be needed to solve problems dealing with general graphs containing possible cycles. Not coincidentally, this describes many of the GOFAI problems for which GC (in the context of LISP) was first developed.
Weak and unowned pointers are pretty simple to deal with, and I’ve never seen a situation where it’s really confusing whether a pointer should be strong or weak in swift. Everything mostly just works, and if you need to point back to the object that owns you, it’s usually pretty clear by the abstraction you’re building that you need a weak/unowned reference to do it.
That's not the interesting case; that is indeed easily addressed by "weak" pointers. What's interesting is the case of a spaghetti reference graph where none of the objects definitely "owns" any other, and even the choice of GC roots might be dynamic.
Modern C++ unfortunely still has too much people writing classical C with classes, to make it properly safe.
Rust, yes, affine types are great, but their usability is only justified in scenarios where having any kind of automatic memory management isn't acceptable.
With Rust and C++ (don't know about Swift), you still have to spend a lot of time thinking about memory.
You don't think about memory, you just think about how to ensure that you're always referencing live objects. And you need that kind of thinking anyway to deal with non-memory resources, where a GC won't help you at all.
There are other ways such as customozong new/delete.
Depends on your situation what you can or cannot do/afford.
Haskell needs a GC too. You can't do ergonomic functional programming without GC (it has been attempted).
Optional GC, also means you are free to go for it manually, if there is really and truly some bit of extra performance needed. In regards to language wars, it's arguably better to make the case of how to make manual memory management more convenient to use, when the optional GC is turned off.