Do the language designers intentionally chosen the same acronym to confuse us?
Do the language designers intentionally chosen the same acronym to confuse us?
(On 64-bit) Rust very naively has two 64-bit integers for the strong and weak count, Swift packs them into only one. Swift also packs in several extra flags for various things [0].
These flags mean that retain/release (increment/decrement) is actually an atomic compare-and-swap instead of a fetch-add. Allegedly performance issues with this were fixed by the hardware team, just, optimizing CASes better.
Swift also has to interop with ObjC "weak" pointers which have move constructors because their address is registered with a global map which is used to null them out when all strong counts go away, but I don't think this changes the design much when not using them.
Swift ARC is built into the language and a huge amount of the compiler's energy is dedicated to optimizing it. This is why it's part of the calling convention (+1/+0), why there are special getter/setter modes with different ARC semantics, why many stdlib functions are annotated with "this has such-and-such semantics" and so on.
Swift ARC is also very pervasive, as basic collections are all ARC-based CoW, all classes are ARC, and I think existentials and implicit boxes also go through ARC for uniformity? You can in principle avoid ARC completely by restricting yourself to value types (structs/primitives) but this is complicated by polymorphic generics and resilient compilation necessitating some dynamic allocations.
ARC is also why historically Swift gave itself fairly extreme leniency on running destructors "early" based on actual use [1]. Eliminating a useless +1 can be the difference between O(n) and O(n^2) once CoW gets involved!
By contrast in Rust it's "just" a library type which you have to clone/drop (increment/decrement) manually. It doesn't do anything particularly special, but it's very predictable. The existence of borrows in Rust lets you manually do +0 semantics without having to rely on the compiler noticing the optimization opportunity, although you do need to convince the borrow checker it's correct.
[0]: https://github.com/apple/swift/blob/3b00177f768b630a8f7a1135...
[1]: https://forums.swift.org/t/a-roadmap-for-improving-swift-per...
Swift builds the behaviour into the language runtime for objects where the behaviour applies, while Rust eschews language runtimes and provides it as an explicit wrapper structure in its standard library.
I haven't written Swift, but I believe reference counting is mostly automatic and transparent there, more like a GC strategy than like a container.
Not sure whether anyone's to blame for the name collision :)
- Like you said, Rust doesn't let you data race on the contents of an Arc.
- Rust also doesn't let you data race on the Arc itself (i.e. repointing a global Arc without synchronization). This is Bug #5 in this great talk: https://youtu.be/lkgszkPnV8g
- Rust also doesn't let you retain a reference to the contents of an Arc longer than you retain the Arc. This is the bug that C++ Core Guideline R.37 is about: http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#...
But I'm not sure I'd call these Arc vs shared_ptr differences per se, because almost any comparison between a Rust container and its C++ equivalent looks similar.