So given Objective-C's semantics, having the compiler insert [... retain] and [... release] calls according to Cocoa patterns, makes more sense than the original GC idea.
Which only works for Objective-C classes that follow such patterns, not for plain structs or basic C like data types.
Swift also adopted ARC, because it needed anyway to interoperate with the Objective-C runtime.
RC is nothing new, it was the very first GC algorithm being implemented, there are many reasons why GC research moved beyond it.
Making RC algorithms achieve the same kind of performance in multi-core core using NUMA architectures (multi-level caches) is as complex as just having tracing GC algorithms, with the caveat of not handling cycles and possible stack overflows due to cascade deletions.
It's not about RC, it's about the A in ARC.
Removing redundant increment/decrements was a well known optimization long before Apple added that A to RC.
What is the technical difference at implementation level?
That is, for each RC system of a certain complexity / feature set / etc., there exists a corresponding GC system that requires the same amount of work, and as the RC system does more work, so does the GC system. The exact times at which you have GC pauses, or pool cleanups, or whatever you want to call them are different, but the total amount of overhead is the same.
Of course, it's still worth thinking in one of the two paradigms: for instance, if you need precise timings of a function's execution (but it's okay for that function to be slow), you may prefer the immediate overhead of refcounting instead of amortizing the cost into some later garbage collection. But if you can optimize your refcounting overhead based on some application-specific knowledge, loosely speaking, you could also optimize a corresponding GC system by using the same insights.
(Yes, really. Adding atomic operations to every load and store of a pointer is a tremendous throughput loss. Latency is not everything!)
But I think a deeper reason might be that making reference counting reasonably fast depends on the compiler proving that certain objects aren't shared across threads. If RC itself were slow, nobody would use talloc().
1) Ownership model means RC only needs to be used rarely in Rust, and ARC even less, minimizing the throughput impact?
2) Simplicity benefits were overwhelming considering the targets you were aiming for?
Or something else?
Yes, they don't enforce you to properly specify and transfer ownership semantics at compile time with the borrow checker (although you can enforce some of these things with modern C++), but the cognitive overhead is there. And if you make a mistake you don't get a scary compiler error - you just get a hard to find memory leak, an even harder to find data race or a gaping security hole.
Even Go (which usually touted as the perfect alternative to Rust in these conversation), has a lot of cognitive overhead related to ownership. Every time you take a byte slice and pass that forward to another function, you have to know whether that function appends to that slice, in which case your original slice will get overwritten. Case in point:
arr := []byte("Hello World")
b1 := arr[:6]
b2 := append(b1, 'Z')
fmt.Println(string(b2))
fmt.Println(string(arr))
https://play.golang.org/p/QIN5SAIL5MMany byte manipulation functions in the go std library and ecosystem have append semantics, for instance: https://godoc.org/regexp#Regexp.Expand
Better yet, Go often makes it very hard to notice whether you're copying values or just assigning a reference. On the other hand, some languages like Java don't even let you copy values reliably without special support in the class and in the same time highly encourage doing everything with stateful mutable objects. You can't just go and pass all these neat POJOs and beans around to functions modifying their state if they get used around somewhere else, right? This is exactly what all these rust borrow checker warning are all about!
The only language I can think of which is completely devoid of ownership semantics overhead is Haskell. Everything is immutable, look ma no side effects! So if this is really the only benchmark for productivity, Haskell is the most productive language ever.
> ARC makes no guarantees in the presence of race conditions.
So, it seems they agree with you, to the extent that they agree that full atomic reference counting would be a bad idea for performance optimisation, which is why they don't do it.
The race conditions that quote is referring to other kinds of races, not races on the reference count itself.
Personally my go-to solution where I need the flexibility of GC is Lua. It's light-weight, stupid fast for what it is(thanks to LuaJIT). You can also constrain it really easy and running multiple instances is very well supported. If you partition your problem space right you can keep each domain small and thus your root set doesn't grow like it does in JS/UnrealScript/etc. There's actually a lot of parallels with Erlang's root-per-process GC and it.
Oh and it's also stupid-easy to drop into any C codebase.
Because ARC is just shitty GC.