Rust and Swift (iv)
Language design trade-offs, highlighted by string manipulation.
I am reading through the Swift book, and comparing it to Rust, which I have also been learning over the past month. As with the other posts in this series, these are off-the-cuff impressions, which may be inaccurate in various ways. I’d be happy to hear feedback! Note, too, that my preferences are just that: preferences. Your tastes may differ from mine. (See all parts in the series.)
Both Swift and Rust directly address the issue of having to worry about memory allocation and safety. They do it in different ways, though: Swift by automatic reference counting, Rust by its concept of ownership. For a lot of day-to-day development, I can see the Swift approach being a win for the same reason a language like Python or Ruby is: having that all handled for you is nice. Having the power Rust gives you comes at the price of increased cognitive load from having to reason about ownership.
To put it another way: all programming languages have to make trade-offs. Although I like Rust’s better than Swift’s so far, I’ve no doubt I will find any number of things to appreciate about Swift over Rust. You can’t have everything.
This caught my attention in part because dealing with things like strings (or other pass-by-value types) in Swift is rather more straightforward than in Rust. The outcomes are much the same, but since all String
s in Swift are passed by value (never by reference), you simply don’t have to think about modification—even safe modification!
Rust of course had the Copy
trait which lets you do this, but the point is that the “ergonomics” are slightly nicer in Swift.
Also, the string interpolation Swift does is nice. That’s one thing I really wish Rust had. It’s Python-style string formatting macro is great, but being able to interpolate values ("strings with \(variables)"
or even "embedded expressions like \(2 + 4)"
) is very nice.
Swift’s approach to strings in general seems well-thought-through and gives appropriate levels of attention to the details which make handling complex or non-Western languages much more manageable. As a typography geek, I appreciate this a great deal.
That said, since Swift’s strings do handle all those edge cases for Unicode, you lose some standard string access patterns and lose much (maybe all?) insight into the internal structure of the string. That may be good, and may be bad, depending on the circumstance. Like I said: trade-offs.
Actually, on reading further, the way Swift handles Unicode strings is pretty nice. It does give you insight into those, via specific methods for different representations. I particularly appreciate that it’s you deal with them as the standalone String
type as well as giving you direct access to the code points—and not just one Unicode code point set, but any of UTF8, UTF16, or UTF32 (Unicode scalars). Trust Apple to pay close attention to text.
Rust’s strings are good, but not quite as sophisticated (presumably for simplicity around the memory mapping). All Rust String
or str
instances are composed of UTF32 Unicode scalars, encoded as UTF8 sequences. It doesn’t have some of the convenience methods Swift does for getting any of the other representations. That said, I expect this should show up rarely if at all in my ordinary usage. Importantly, the fundamental storage is the same: both use scalars.
This was the first section where it didn’t feel like Rust was just a clear overall “winner” over Swift. Some of the trade-offs between the language designs are more apparent here, and I do appreciate the “ergonomics” of Swift in a number of these things.