Swift observations from a reluctant Rustacean
November 16, 2024
Recently I've been thinking about Swift in terms of Rust, & have appreciated anew some of the choices made. In Rust. There's been a proliferation of X vs Y posts on the web, especially since the advent of LLM AI, so I try to resist the format. It's often easier for me to think about a programming language in terms of another programming language though, & some comparisons seem inevitable.
In Swift, value types like structs are cloned rather than moved (to use Rust terms). Built in types only do this when something changes, & arrays are the most obvious example of this copy on write optimisation. But for a struct that you or I might write the cloning always happens. Every time. Every time you pass your struct to another variable, in a function call say, unless you were to wrap your struct in a reference type & write your own copy on write implementation, you'd get a whole new copy. Every, single, time. In Rust cloning is manual, & only needs to happen if the value's going to be re-used as well as moved. For library authors I guess especially in Rust there's also the Cow type, Cow standing of course for copy on write.
Reference types in Swift use ARC (atomic reference counting).
Edit 17 November 2024 – A for Atomic in Rust. A for Automatic in Swift. Though Swift ARC also uses atomic operations for its reference counting. A for argh-proofreading.
I suppose that's because the last iterations of Objective-C used ARC, & Swift was designed to be a drop-in replacement. Oddly enough I remember a lot of resistance to using ARC from amongst Objective-C devs at the time, but officially at least that's how it worked. Arc<T>, the slower version of Rc<T> allows for shared ownership that's thread safe, in Rust. In Swift, where all references are shared & potentially mutable, it's not necessarily thread safe at all. Would separate Rc & Arc implementations in Swift make more sense now? Maybe. But look at the resistance to Swift 6 & I think you might get an appreciation of the chance of that happening. As it is the more expensive atomic counting happens all of the time in Swift.
Lastly, errors. Returning error Results seems to be more consistent & somewhat better thought out in Rust. Swift's dual use of throwing & returning Results, with optionals sometimes also returned, is far muddier.
All of this isn't to denigrate the design choices made with Swift, or how people use it. All of these choices are trade-offs after all. Rather it's meant to be hopeful about my own Rust programming. I recently picked up a copy of Rust for Rustaceans, which is meant to be for intermediate Rust programmers, & oh boy. Intermediate? One day maybe. I've been a beginner Swift programmer for ten years now, & an ocassional & very much beginner Rust programmer for at least eight. But for now, if I do these things, and better than these things, then my resulting Rust code will be at least as good & at least as fast as the Swift equivalent, & potentially a whole lot better & faster, because this level of optimisation seems like low hanging fruit. But this comparison isn't just about the end product. Or even the craft of it all. Rust has one advantage it seems Swift can never have.
I recently wrote, "Even if the only thing Rust had going for it was that it wasn't beholden for its existence to one of the giant tech companies that would still be a lot." Those chickens have now come home to roost. The US has had its election, & all of us are left to deal with the fallout from that. Tim Cook didn't need to rush to congratulate the President-elect. But he did.