A C++ Hacker Learns Swift

I recently had some free time and decided to finally dig into The Swift Programming Language ebook that’s been sitting on my phone since last year. I watched the WWDC keynote last June where Swift was introduced, and thought it sounded interesting, but I filed it away as “something to check out sometime”. Fast-forward a year, and I’ve finally gotten around to it (ironically, I’m writing this mere hours before the 2015 WWDC keynote, where a new version of Swift will most likely be introduced and an updated version of the book rolled out, but such is life…) [EDIT: my Swift 2.0 overview is here]

Some background information might be helpful at this point: I’ve been a full-time triple-A game developer for nine years (specializing in User Interface code), and consequently the vast bulk of my professional coding experience has been in C++. I’ve worked occasionally with Java, JavaScript, and ActionScript, and have dabbled in PHP and C# (and SQL, for what it’s worth), but C++ is far and away where I spend most of my time.


That said, from my perspective I found the official Swift reference a thoroughly compelling read, and Swift a thoroughly compelling language. I read it cover to cover, and it makes me excited to try a project using Swift at some point.

It bears mentioning that the book itself is very well-written — clear, concise, and generally making very few assumptions about the reader’s prior knowledge (a detailed explanation of two’s-complement binary integer representation is provided, for example). And while the tone remains steadfastly professional, the code samples provide some sly references for the well-read nerds out there:

…and of course, it makes my towel-toting heart glad to see all of these:

I’d also like to congratulate the authors on resisting the “foo/bar/baz” urge — every sample variable and function shown throughout the book has either a descriptive name (func addOne(x:Int) {…}) or a descriptively generic one (let someInt = …), keeping the clarity and readability consistently high.

So What’s So Great About Swift?

At a high level, Swift, like Java or C#, aims to provide an elegant, modern, full-featured language, with an emphasis on safety via automatic memory management. Unlike Java and C#, which use garbage collection to achieve that aim, Swift uses automatic reference counting, so that resources are automatically freed as soon as they are no longer in use. Safety always comes at a (performance) cost, but Apple is clearly betting that the performance cost of Swift’s ARC will be low enough to make it a compelling language for a broad range of (Apple-centric) uses. For my part, I’m a big fan of Jonathan Blow’s observation that optimizing for faster development time is often more important than optimizing for CPU or memory usage, and I think Swift is a clear step in that direction (though Apple aims to have their cake and eat it, too: “The compiler is optimized for performance, and the language is optimized for development, without compromising on either”).

Indeed, the biggest criticism of Swift I’ve encountered in my Googling is its performance, although it seems to be already quite good in many scenarios, and steadily improving with each release.

Syntax

The first pleasant surprise with Swift compared to Apple’s “other” language, Objective-C, is its familiar and friendly C++/Java-style syntax: no more wacky bracketed message-passing (no, I’ve never used Smalltalk), just the classic dot-syntax for members and parentheses for function calls that K&R so elegantly laid out almost forty years ago. Amusingly, the book specifically points out that Swift, in contrast to Objective-C, breaks free of “the constraints of C compatibility” under the hood — while at the same time moving (back) closer to C syntax.

Syntax was clearly a big focus of Chris Lattner and the Apple Developer team for Swift: not only making it familiar for C++/Java coders, but also making it better than those languages by removing as much boilerplate as humanly possible. Swift’s type inference is the most obvious manifestation of this, and it’s delightful — a strongly-typed (ie. performant) language that will figure out the types I’m using without me having to tell it every time, everywhere? Sign me up.

Other syntactic touches like being able to (optionally) delineate numeric literals with underscores for readability (10_000_000 vs. 10000000), being able to specify binary literals (0b11110000), not requiring semicolons at the end of every statement (a habit that will die hard for me, but a gesture I nonetheless appreciate), nested range comments (/*a /*nested*/ comment — epic!*/), a modulus operator that works for floats as well as ints (5.3 % 2 == 1.3), closed and half-open range operators (1…3 includes 1, 2, and 3, while 1..< 3 includes only 1 and 2)… the list goes on.

Perhaps my favourite illustration of Swift’s fetish for syntactic minimalism is this example from the Closures chapter, which gleefully details six increasingly concise ways to write the exact same code:

The example is of course carefully chosen to specifically demonstrate this sort of reduction — I imagine that few real-world applications will be looking for closures simple enough to be expressed in a single character. But I still like that they’re thinking this way. Any time I can do something in fewer lines (and characters) of code, ideally without sacrificing readability or performance, I’m a happy coder.

Safety

Occasionally Swift does choose to require slightly more boilerplate than its C++ equivalent, though it generally does so to improve clarity and reduce the chance of mistakes, and it’s hard to find fault with that. Granted, in C++ I do sometimes like to skip the braces around a single-line if statement, or omit the (empty) default case in a switch, but I can see the rationale for always requiring them, and it’s not a big deal to make those adjustments.

Further to that point, safety has clearly been a watchword in the development of Swift. The ARC-based automatic memory management is the most prominent example, of course, but the braced-if and exhaustive-switch requirements mentioned above are just two of the many other language decisions motivated by safety. An explicit (not coerced) Bool is required for if statements, preventing the classic “if (a = b) {…}” error. All integer operators are overflow-safe by default (var myNum: UInt8 = 255; myNum++; will trigger a runtime error, for example), although alternate overflow-friendly operators are available if needed. Combining safety and brevity, case statements in a switch statement break by default — if you wish to fall through to the next case, you must do so explicitly with the fallthrough keyword, preventing the hard-to-spot accidental fallthrough that has bitten me more than once in C++. Loops and switch statements can optionally be given a label, and that label can then be referred to by any break or continue statements nested therein, so that you can make the intention and scope of your break / continue explicit.

The argument could be made (and I’m sure has been) that handing off all this safety logic to the compiler and runtime just makes programmers lazier and less careful, while making the resulting machine code slower and more bloated. While that’s probably true, at least to a degree, the same basic argument could be made against every programming innovation of the last (n) decades. Why program in Swift when C++ is faster? Why program in C++ when C is faster? Why program in C when assembly is faster? Obviously, performance is a factor, but if history is any guide (and if Moore’s Law keeps on truckin’), the eventual success of increasingly higher-level languages like Swift is a good bet.

Batman’s Utility Belt

Interestingly, while Swift embraces syntactic minimalism, it arguably offers language-feature maximalism, with an impressive laundry list of natively-supported bells and whistles:

These features all seem to play pretty well with each other, at least in the confines of the language reference — the answer to the question “I wonder how (x) works?” seems to generally be “The way you’d expect it to.” And should you get bored with all that stuff, why not start defining your own custom operators, complete with custom precedence and associativity?

Bones To Pick

Of course, per the Stroustrup quote above, Swift is apparently a language that people use, so it is therefore a language people will complain about. I’m happy to prove the rule:

  • using Int/UInt8/16/32/64 for explicitly-sized integer values, and just Int and UInt for “natively-sized” values, makes perfect sense. So why stick with “Float” for an explicitly sized 32-bit floating point number and “Double” for 64? Wouldn’t it have been more consistent to use Float32 and Float64, plus just Float for the current natively-sized version? “Double” is a bad name — it’s non-descriptive, has no apparent semantic connection to “Float”, and basically only makes sense if you’re already used to it. An obvious missed opportunity for improved clarity.
  • similarly, it feels like an admission of defeat to read: “Use UInt only when you specifically need an unsigned integer type with the same size as the platform’s native word size. If this is not the case, Int is preferred, even when the values to be stored are known to be non-negative.” Given Swift’s emphasis on expressiveness and safety, why would it be recommended to allow negative values for a quantity I know should never be negative? (I understand the technical reasoning, but it feels like a concession, in a language that fights hard not to have to make that kind of concession)
  • overall, Swift does a very good job (in my opinion) of cutting its syntactic ties to Objective-C, while still (impressively) maintaining interoperability with Objective-C libraries and code. One odd exception, however, is that the external name behaviour of methods (but not functions) matches Objective-C by default: the first parameter is anonymous, while any subsequent parameters require their external name. Supporting such behaviour is perhaps a requirement of interoperability, but why make it the default? It feels strange to have a method like, for example: func moveTo(x:Int, y:Int) { } which then requires a calling syntax of obj.moveTo(5, y:10). Yes, I can explicitly force y to be anonymous (func moveTo(x:Int, _ y:Int) { }), but why should I have to?
  • it seems like a strange limitation that subclasses can only call designated initializers of their superclass, not convenience initializers — since convenience initializers by definition immediately delegate to designated initializers, why the restriction? Perhaps there’s an under-the-hood reason (or a more obvious one that I missed), but it seems like an unnecessary prohibition to me.

The Wrap-Up

Unsurprisingly, I have many other thoughts on Swift that haven’t made it into this post yet, but it’s late and I need my beauty sleep. I haven’t discussed (nor does the book, for that matter) Playgrounds, or Swift’s unusual take on access control (public vs private members, methods, and types), or curried functions, or failable initializers, or…

But for what it’s worth, I’m sure I will have reason to revisit this subject in a future post. Stay tuned.

For now, suffice it to say that Swift seems like a promising language with a ton of neat tools and a sparkling-clean syntax that I kind of love. If this post has piqued your interest, I highly recommend reading the official guide, and/or installing Xcode, and/or just loading up swiftstub.com and playing around with it.

Happy coding!

Like what you read? Give Brook McEachern a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.