Impressions of Swift from Go
I'm helping with an iOS class for high school students during the summer. I used to do iOS development professionally a while ago, but that was back in the Objective-C days. I've done some minor stuff in Swift - and it doesn't seem particularly hard - but I haven't used it extensively or regularly, so I decided to do a small project to refresh my skills a bit.
Today I mainly use Go, so I thought it might be interesting to write down some of the differences that came up and minor frustrations. This obviously isn't a comprehensive (or even fair) language comparison - it's only one small project, and I'm not equally skilled in both languages.
First, the project. It’s a simple Tetris-like game:
It’s not complete - there is no score or lose screen. But the basic functionality is there. All in all it took around 3 hours, which I’m reasonably pleased with considering I’m not totally fluent in Swift and spent a lot of time looking up stuff (although I am familiar with the basic frameworks from Objective-C, otherwise it would have taken considerably longer). It’s around 250 lines of code including all the comments and whitespace (available here).
Representing the Board
In Swift, I elected to represent the board and pieces like so:
First of all, because I wanted to get it working as quickly as possible, I just hardcoded some of the numbers into the project. I would abstract things a bit more if I intended to develop it later, but I don’t.
Similarly, I could put the block data into some sort of Piece object, but since there’s only one bit of data per piece that I care about (the four blocks that it’s composed of), I opted to cut out the middleman.
For this particular game, the board is a 10 blocks wide and 15 blocks tall. In Go, I would represent this as a fixed length array of fixed length arrays, [15][10]int (or more likely an 8-bit type).
In Swift, I was unsure what to use. It looks like fixed-length arrays are not supported, and neither are multidimensional ones. I could allocate each row separately and put them into a larger array, but initializing that seemed like it would take a few extra lines of code.
Instead, I decided to allocate an array containing all the blocks and do the math myself, indexing it using blocks[10*y+x]. On the other hand, because Swift makes it easy to fill the array with a non-zero value when creating it, I opted to use -1 as the empty value where in Go I would use 0. This simplifies some of the other code a tiny bit, since the pieces are 0-indexed we use the 0 value to represent the block color for piece 0, 1 for the color of piece 1, etc.
Switch Statements Must be Exhausting
Here is a function to get one of the four blocks of the current piece (with the current transformation applied):
(In Go, I would likely write this function to return a fixed-length array for all the blocks in the piece.)
Anyway, notice the error: “Switch must be exhaustive”. That’s easy enough to fix by adding a default case. But that produces another error:
To fix this, we also add a break:
This seems a little excessive (especially since, like Go, and unlike C, switch statements break instead of falling through by default).
We do the same thing for handling input, adding a default: break to handle the keys we do not respond to.
I'd like to add that Swift may actually win the switch-statement-convenience-war overall. If you use switch statements mainly with enums (and want to handle all the cases), Swift will enforce that at compile time. In Go, I've gotten into the habit of usually adding a default case for switch statements anyway (and basically always for enums). But adding default: panic() is slightly more verbose and certainly less convenient than catching the error at compile time.
Of course, Go doesn't really have enums - it more or less gets by with using other language features for the same purpose. And I think that's also a reasonable choice - the way it works currently (in Go) hasn't been a major issue (at least in my experience), and adding a new fundamental type to Go to address a minor convenience issue seems like overkill. If a run-time error is unacceptable I think static analysis could find these issues pretty easily.
Swift is a different language of course. Even requiring the break could be explained by the transition from Objective-C; they may want to discourage certain code patterns that will work differently in both languages. Perhaps someday when fewer people use the old language they could remove the requirement.
Tuples vs Multiple Return / Assignment
Swift uses Tuples where in Go I would use multiple return values or multiple assignment. For example in the rotation code shown earlier, there is this statement:
In Go we would achieve the same thing using multiple assignment (just remove the parentheses). Tuples have some advantages if you want to pass them through functions before inspecting the contents, but in the few places they were used in the project there was an immediate destructuring assignment so Swift actually ends up slightly more verbose (because of the parentheses). But not a big difference either way.
Fixed-size Arrays vs Tuples
Swift Arrays are similar to Go slices (they are both reference types), and there is some similarity between Swift Tuples and Go fixed-size arrays, in that they are both value types. I considered representing pieces as a four-length tuple of coordinates instead of an array. (edit: In the context of thinking about efficiency here both Swift Arrays and Go slices contain pointers to the underlying data, but it may be inaccurate to call them reference types, since neither is purely a reference type from a semantics perspective.)
But there is an important difference: fixed-size arrays can easily be indexed, but Swift tuples can not. This is likely because tuples can contain heterogeneous elements of different types. There is a strange syntax for accessing the elements using (for the first element) myTuple.1 (with no square brackets) but this won’t work for indexing them dynamically.
I maybe could have used tuples to represent the coordinates, since skimming the code I don’t see any places where I dynamically choose between the X or the Y coordinate.
Named Arguments
Swift shows its Objective-C heritage here. Named arguments add some clarity (and verbosity), although the Go IDE I use can show the parameter names at the function call site in a similar way (without the argument names being actually present in the code). There’s a way to omit the requirement for the caller to name the arguments by adding a ‘_’ like so:
In Go, it’s possible to omit the type for multiple arguments in a row of the same type, for example this function would be func fillRect(x, y, width, height float64). I don’t know if Swift has a similar feature, it’s certainly more verbose here.
Variadic Functions
This actually didn’t come up in the project, but on the topic of function calls: is it possible to wrap variadic functions in Swift? For example, if I wanted a myPrint() function that calls the system print(), but also wrote to a log file. This answer on Stack Overflow suggests it requires a feature that hasn’t made it into Swift yet, but I found it surprising that this was (apparently) impossible. Go also has variadic functions and no problem with this.
Loops and Ranges
Swift doesn’t have C/C++/Java/C#/JavaScript/Go etc style for loops. In fact, it used to have them, but removing them was apparently so essential it was necessary to break backwards compatibility.
Some of the uses can be replaced with ranges, for example for i in 0…3 or for i in 0..<4. There is also a construction like for i in stride(from: 0, to: 10, by: 2) . And for everything else, there are of course while loops.
Honestly, I can’t comprehend the priorities that lead to breaking backwards compatibility to remove such things (in a similar case, they broke backwards compatibility in order to remove the ++ operator). The level of complexity in these concepts is minuscule compared to the rest of the language (let alone the frameworks), and Swift has a tendency to add many small conveniences in other areas that increase the learning curve compared to Go. And if the user already knows any of the above extremely-popular languages or intends to learn any of them they will need to learn these concepts anyway. In particular, I don’t think JavaScript is going away any time soon.
Nevertheless, while loops are an adequate replacement, so I can’t say this is a huge deal (although I would be annoyed if I had to update a large codebase). I just don’t understand how this was worth a backwards-incompatible change over, or what its absence adds to the language other than slightly more typing.
Optionals
I didn’t need to make much use of optionals in this project (I believe I only use them for the animation timer). Optionals seem like a feature that would benefit beginners, but one thing I’ve realized from working with beginners is that they sometimes aren’t interested or maybe able to use them properly. I’ve seen too much code where you have 10s of lines calling methods on a nullable object, where clearly ‘.?’ and ‘.!’ have been added seemingly-randomly to every method call until the compiler warnings go away (I’ve even seen this in a (successful!) commercial project). It of course also increases the learning curve. This leads me to suspect there is a window where people get the most benefit, maybe starting off somewhere past the absolute beginner level, then tapering off as people learn habits that avoid these errors in other languages.
In Go, I avoid making variables sometimes-null, preferring to initialize them with a non-null value shortly after creation (although it not being a strict requirement by the compiler provides some flexibility around initialization). Again, I think the way Swift does it is also reasonable and I can see the advantages of being strict here.
One thing I’m curious about is how Swift handles the intersection of optionals, generics, and value types. Specifically, covariance. For example, if you have an (immutable) Array<Thing>, can that be passed to a function expecting Array<Thing?> ? Also for functions, is func(Thing?) usable where a func(Thing) argument is accepted? In a language where nullability is human-checked, a manual type-checker could reason that those programs are correct (and they would of course run correctly).
For reference types, it seems like it would be straightforward to implement, but an optional value type will not have the same memory layout as a non-optional value type, so it would be a challenge to emit generic code that works with both (without some layer of indirection that could add a substantial performance penalty for using generics). Of course some popular languages avoid memory layout issues by (for example) individually heap-allocating every Double in a list, but that’s terrible from a performance and memory use standpoint if your application is doing things like that much. But Swift, like Go, doesn’t box everything.
This answer implies that covariance is limited to certain built-in types blessed by the compiler. Maybe when I have time, I’ll try to learn how far they are supported and if certain uses have a performance penalty.
Closures / Blocks
Swift functions can take a block argument, which is used in the game for the animation timer:
Note that advanceFrame() and setNeedsDisplay() need to be called with an explicit self in order to indicate that self is captured. In general, I feel closures are less comfortable in Swift since inadvertent cyclic references can cause memory leaks.
Also, note that the { } after the call to scheduledTimer(), is actually a third, block, argument to the function call even though it is outside the argument list. There is also a special in syntax for the closure argument. In Go the closure syntax more resembles ordinary functions, and they’re passed like ordinary arguments. I don’t really have a problem with Swift’s choices here, but it still boggles my mind that a language that adds stuff like this needed to break backwards compatibility to get rid of the the ++ operator.
Generics
I suppose I'm obligated to mention generics here. Go has two built-in generic types, similar to Swift's Arrays and Dictionaries. But (currently) that's it - if you need to use something like Java's ConcurrentHashMap a lot it will be an inconvenience, requiring either writing a type-safe wrapper or casting values and leaving open the possibility of run-time errors. I think how big a problem this is clearly depends on what sort of program you are writing - for this project, arrays are sufficient, so the availability of user-defined generics did not make a difference between the two languages. Also, as of this writing, a generics system for Go has been designed by the language team and is set to be added to the language in the near future.
Advantages and Disadvantages
Overall, I think most of these differences aren't super important - I could see myself being productive in both languages. For this small project, the Go feature that I miss most in Swift is the fixed length arrays. I suppose the performance doesn't really matter here, but I expect that Swift is doing a bunch of tiny heap allocations (one for every block for every piece), where Go would be able to allocate one big chunk. It would also have made creating the game board more convenient.
I think the Swift feature that I would miss most doing this project in Go would be the scoping rules for instance variables. Go methods require the object to be passed similarly to a regular parameter and to use that parameter for field accesses. Maybe there's some advantage to the explicitness but for a project this size I prefer just having the instance variables in scope.
Final Thoughts
This particular project didn't really involve what I think are actually the most important differences between the languages (aside from generics), mainly that Go has a cycle-aware garbage collector and a different concurrency strategy. Like Go and generics, Swift is going to be going through some changes on the concurrency front, but it looks like major differences will still remain between the two languages in that area afterwards.
And of course for choosing between these languages in the real world ecosystem considerations are probably more important than any specific language features. There's not much overlap between the areas Go and Swift would ordinarily be used for, although there is server side Swift and surprisingly enough there are also some iOS apps using Go.