Swift – The Future for App Developers?

Myself, like most developers watching the WWDC keynote, I'm sure were shocked when Swift was announced. I think many of us have longed for a world where header files were a thing of the past, a syntax that could be freed from the C days of coding, and a generally safer language where it is harder to make little mistakes that could cause hours of debugging over a stupid mistake.

However, once developers started to play around with Swift, we realized a few sad truths:

  1. While the language is "ready for use", it is only so in the most basic of ways. Many features are missing, there are many rough edges, and there seems to be some fundamental design flaws in the language. And lots of editor and compiler crashes.
  2. The language promised performance, and while it may be faster than ObjC, many in the community are having a really hard time finding where that is true. Apple, please publish the code for your benchmarks so we can see the kind of Swift code we are supposed to be writing.
  3. The baggage of C may be mostly gone, but the language has clear design trade-offs to support better integration into ObjC. This doesn't seem like a win to be but rather the easiest way to bridge Swift and ObjC.

Before diving into the specifics, let me be explicitly clear: I think Swift is a step, a great number of steps in fact, in the right direction. And if some of flaws are fixed, the language will be a great improvement for the future of iOS and Mac development.

Header Files—Where are you?

Header files are gone! The only thing header files were good for, in my opinion, were create a nice contract for external consumers of your code. Other than that, they were a nightmare, especially in large projects. I've spent many hours tracking down symbols because other developers would simply not put all of their dependencies in the header files… I'm ecstatic that is no longer a problem!

The other great thing about no header is that we can take code that looked like this:

@interface Person {
  @property (copy, nonatomic) NSString *firstName;
  @property (copy, nonatomic) NSString *lastName;

  - (instancetype)initWithFirstName:(NSString *)firstName
                           lastName:(NSString *)lastName;
  - (NSString *)fullName;
@end

@implementation Person {
  - (instancetype)init() {
    return [self initWithFirstName:@"" lastName:@""];
  }
  - (instancetype)initWithFirstName:(NSString *)firstName
                           lastName:(NSString *)lastName {
    self.firstName = firstName == nil ? @"" : firstName;
    self.lastName = lastName == nil ? @"" : lastName;
  }

  - (NSString *)fullName {
    return [NSString stringWithFormat:@"%@ %@",
            firstName, lastName];
  }
@end

And define it as this:

class Person {
  var firstName : String
  var lastName : String
  var fullName : String { return "\(firstName) \(lastName)" }

  init(firstName : String = "", lastName : String = "") {
    self.firstName = firstName
    self.lastName = lastName
  }
}

This is such a big win. There are also some implicit differences about this contract:

  • Both the firstName and the lastName are enforced, at compile time, to never be allowed to be set to nil.
  • The member fullName is actually a computed property and not a method. This is only semantically different, but it provides an explicit statement of being a property of the class instead of a method that does work on the class.

Syntax—Nearly Perfect

I know I'm one of the few that actually enjoy the square-bracket syntax, well, as long as the nesting doesn't get too crazy. However, Swift takes us nearly all of way into the syntax of some of the more functional languages and some other scripting languages, like Python. This is just something you're going to have to get over (unless we can convince Apple to change its mind!).

Safe-by-default has also greatly added clarity to switch-statements as well. The ability to fallthrough while preventing accidental fallthroughs can only be seen as a win.

Closures are a nice addition, though semantically the same as blocks. However, we start to tread into some murky waters when looking at closures, the trailing closure syntax, and braces in general.

Swift, unfortunately, is still a curly-brace based language. This means that we write code like this:

var name = "David"
if name != "David" {
   println("Why is your name not David?")
}
else
{
  println("That's a great name!")
}

Now, I've purposely done something that will cause many holy wars across any code review: mismatched braces! Which line should the curly brace be on? Of course, the first is correct… or is it the second. I believe Swift should have simply fixed this issue as it's actually not even important.

var name = "David"
if name != "David"
  println("Why is your name not David?")
else
  println("That's a great name!")
endif

I think this is actually cleaner and creates less options for the developer. While options can be good, they should only be there if needed. Curly-braces have long not been needed in the world of programming.

Let's look at another place where this is actually an issue.

func measure(block: () -> ()) -> NSTimeInterval {
  let startTime = NSDate()
  block()
  let endTime = NSDate()
  return endTime.timeIntervalSinceDate(startTime)
}

Here we have a method that takes a closure and returns the amount of time it takes to run it.

measure({
  var x = 10 + 2
})

measure(
{
 var x = 10 + 2
})

measure() {
  var x = 10 + 2
}

measure()
{
  var x = 10 + 2
}

Unfortunately, only three of these are actually valid code; the last version is not valid and will result in two compiler errors. This is a parser limitation. Instead, had Swift gone away from curly braces all together, this problem could have been avoided.

func measure(block: () -> ()) -> NSTimeInterval =>
  let startTime = NSDate()
  block()
  let endTime = NSDate()
  return endTime.timeIntervalSinceDate(startTime)

measure() =>
  var x = 10 + 2

measure() => var x = 10 + 2

Now, this syntax allows for a common syntax for defining functions and closures and it works with trailing closures. There is a trade off though. If you look at the above, it really doesn't allow for defining those closures as a parameter though.

measure(=>
 var x = 10 + 2
)

That is terrible, do not do it.

let fn = () -> () => var x = 10 + 2
measure(fn)

That's the trade-off, and one I would have gladly taken. It's consistent and helps us write the code in a narrow set of ways.

Named Parameters—Um… What?

The named parameter implementation has got to be right up there as one of the roughest edges in Swift. I really do not like the fact that the API author is the one that dictates whether the name is required or not. I can see why this feature was added—interop with ObjC naming. But I do think it's the wrong design, especially for Swift-only code.

Let's take a look at the choices:

func add(x : Int, y : Int) { x + y }
class Calculator {
  class func add(x : Int, y : Int) { x + y }
  class func add2(#x : Int, _ y : Int) { x + y }
}

add(3, 6)                  // note: no names allowed
Calculator.add(3, y: 9)    // note, the x: is not present
Calculator.add2(x: 3, 9)   // note, the y: cannot be present

There are actually different defaults for the name requirements; functions are different than class members. The above is actually the only valid way to call those methods.

The following should be valid:

add(x: 3, y: 6)
Calculator.add(3, 9)
Calculator.add(x: 3, y: 9)

This is one of the places where the ObjC baggage comes across very strongly over into Swift. The Session 404: Advanced Swift WWDC talk gives an example of this and walks us through a story. In that story, we are told that this is just too verbose:

let wallWestOfHouse = Thing(location: westOfHouse,
  name: "wall",
  longDescription: "The plaster has crumbled away, leaving the wood beneath to rot.")

And that it would be really nice to be able simply write this:

let wallWestOfHouse = Thing(westOfHouse, "wall", "The plaster has crumbled away, leaving the wood beneath to rot.")

I agree, it should be. However, my argument is that both should actually be completely valid without putting that in the API and forcing one use or the other. In certain contexts the missing named annotations could be beneficial, but in others, it may not matter.

Not the end of the world, but I think this could have been cleaner.

Immutability—Or Lack Thereof…

At first, when I started to hear the mention of preference for immutable types, immutable by default, only use var when necessary, my hopes were high. There are such great things you can do in the compiler to optimize code that is dealing with immutable objects. However, as the details started to come out, it because clear that Swift doesn't actually have immutable types, only immutable values. This is very dissapointing.

This is the one part of Swift that I cannot seem to wrap my head around this decision and it's clearly not an oversight because there is documentation showing off the feature and how it works.

Let's take a look:

let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0

Source: Swift Programming Langauge: Classes and Structures

The let keyword is supposed define a constant such that "the value of a constant cannot be changed once it is set." What Swift has done though, is take that literally. For all object types, the "value" is the pointer to memory.

In the code sample above, everything is just fine because the memory address for tenEighty is never being re-assigned. I think this is a fundamental design flaw. Essentially, in Swift, it is impossible to create an immutable type. This has got to be addressed before going public.

Arrays come out as a very interesting case of this issue. The reason is that an array is actually a struct, but that struct has a pointer to the collection.

let items = [ "Bob", "Frank", "Sue" ]
items[0] = "Henry"
items

Paste that into a playground and you'll see the output is this:

[ "Bob", "Frank", "Sue" ]
"Henry"
[ "Henry", "Frank", "Sue" ]

However, the following code is not valid:

items += "Sally"

The idea that a developer has to understand how something is implemented to understand what is truly immutable about a type and what is not is broken. The fact that the "constant" array is not semantically immutable, is also broken.

In fact, nearly every useful optimization that we can make about parallelization and thread safety goes out the window because of this fundamental issue with Swift: there is not such thing as an immutable type.

Probably the most baffling reason for me is that Swift already has the correct foundation built to offer the exactly right thing!

struct Point {
  var x = 0.0, y = 0.0
  mutating func moveByX(deltaX: Double, y deltaY: Double) {
    x += deltaX
    y += deltaY
  }
}

var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveByX(2.0, y: 3.0)

let otherPoint = Point(x: 2.0, y: 5.0)
otherPoint.moveByX(10.0, y: 4.0)        // compiler error!

If Swift simply required every method on classes that updated member variables to need the mutating annotation, this problem would be largely solved. The only remaining piece would be the way in which to specificy a constant as a parameter to a method.

Summary

All in all, I'm really excited about Swift and the potential that it brings to the table. I've railed a bit on the weaknesses I've found in the language, mostly because I hope Apple will address them. Being able to play a role, however small it is, for a language that I will probably be using for a great deal of years to come on a platform I love to author on in is a very exciting prospect indeed.

Thanks Apple for a truly epic WWDC! And thanks to all of the Swift authors at Apple that will now have to hear from our criticisms and complaints about the language that you've worked very hard on and impressively kept under wraps for four years.

Swift – The Future for App Developers?