Update: As of 4/28/16, I have rechecked all the snares and they all remain. The currying-related snare will be going away, as the currying syntax has been deprecated.


Swift is accomplishing an amazing feat; it is transforming the way we think about programming for Apple devices, bringing in more
modern paradigms such as functional programming and richer type-checking than the Smalltalk-inspired pure object-oriented model of Objective-C.

The Swift language is intended to help developers avoid bugs by adopting safe programming patterns. Inevitably though, such an ambitious undertaking will produce an artifact that (for now, at least) has a few rough edges, snares that can introduce bugs into programs without any warnings from the compiler. Some of these are mentioned in the Swift book, and some (as far as I can tell) are not. Here are seven snares most of which have caught me in the past year. They involve Swift’s protocol extensions, optional chaining, and functional programming.


Protocol extensions: powerful, but use with caution

The ability for one Swift class to inherit from another is a powerful weapon in the programmer’s arsenal because it makes specialization relationships explicit and supports fine-grained code sharing. But, unlike Swift’s reference types, its value types (i.e. structures and enumerations) cannot inherit from each other. However, a value type can inherit from a protocol, which in turn can inherit from another protocol. Although a protocol cannot contain code, only type information, a protocol extension can contain code. In this fashion, code can be shared by creating an hierarchy whose leaves are value types, and whose interior and root nodes are protocols with their corresponding extensions.

But Swift’s implementation of protocol extensions, being somewhat new and unexplored territory, has a few issues. The code doesn’t always do what one might expect. Since the snares in Swift involve the structure and enumeration value types in combination with protocols, we’ll introduce our example with classes to illustrate it without the snares. When it is recast into value types and protocols there will be a few surprises.

Introducing the example: classy pizza

Suppose there are three kinds of pizza made with two kinds of grain:

enum Grain  { case Wheat, Corn }
class  NewYorkPizza  { let crustGrain: Grain = .Wheat }
class  ChicagoPizza  { let crustGrain: Grain = .Wheat }
class CornmealPizza  { let crustGrain: Grain = .Corn  }

Each kind of pizza can respond to inquiries about its crust:

 NewYorkPizza().crustGrain 	// returns Wheat
 ChicagoPizza().crustGrain 	// returns Wheat
CornmealPizza().crustGrain 	// returns Corn

Since most pizzas are made with Wheat, the common code can be factored out into a default implementation embedded in a common superclass:

enum Grain { case Wheat, Corn }
class Pizza {
    var crustGrain: Grain { return .Wheat }
    // other common pizza behavior
}
class NewYorkPizza: Pizza {}
class ChicagoPizza: Pizza {}

The default can be overriden to handle the exceptional case:

class CornmealPizza: Pizza {
    override var crustGain: Grain { return .Corn }
}

Oops! This code is wrong and thankfully, the compiler spotted the error. Can you? The ‘r’ was missing in crustGain. Swift prevents such errors from slipping through the cracks by forcing the code to be explicit about overriding methods in classes. In this case, the code claimed an override, but the misspelled crustGain did not override anything. Here’s the fix:

class CornmealPizza: Pizza {
    override var crustGrain: Grain { return .Corn }
}

Now it compiles and works:

 NewYorkPizza().crustGrain 		// returns Wheat
 ChicagoPizza().crustGrain 		// returns Wheat
CornmealPizza().crustGrain 		// returns Corn

In addition to factoring out the common code, the Pizza superclass allows code to operate on pizzas without knowing the particular type of pizza, because a variable can be declared to refer a general pizza:

var pie: Pizza

But that general pizza reference can still be used to get specific information:

pie =  NewYorkPizza();		pie.crustGrain	 // returns Wheat
pie =  ChicagoPizza();  	pie.crustGrain	 // returns Wheat
pie = CornmealPizza();  	pie.crustGrain	 // returns Corn

Swift’s reference types (i.e. classes) have served this example well. But if this program involved concurrency, race conditions could be avoided by using Swift’s value types with their linguistic support for immutability. Let’s try pizza with value types.

Simple pizza values

Representing the three types of pizza with value types is as easy as it was for reference types, struct simply replaces class:

enum Grain { case Wheat, Corn }
struct  NewYorkPizza 	{ let crustGrain: Grain = .Wheat }
struct  ChicagoPizza 	{ let crustGrain: Grain = .Wheat }
struct CornmealPizza 	{ let crustGrain: Grain = .Corn  }

And it works:

 NewYorkPizza()	.crustGrain 	// returns Wheat
 ChicagoPizza()	.crustGrain 	// returns Wheat
CornmealPizza()	.crustGrain 	// returns Corn

Generalizing various pizzas with a protocol, and an undetected error

Using reference types, we were able to introduce a more general “pizza” concept with the addition of a single common superclass. The same generalization with value types will require two new items instead of one: a protocol to declare the common type:

protocol Pizza {}

and a protocol extension to define the attributes of the new type:

extension Pizza {  var crustGrain: Grain { return .Wheat }  }

struct  NewYorkPizza: Pizza { }
struct  ChicagoPizza: Pizza { }
struct CornmealPizza: Pizza {  let crustGain: Grain = .Corn }

This code compiles, and can be tested:

 NewYorkPizza().crustGrain 		// returns Wheat
 ChicagoPizza().crustGrain 		// returns Wheat
CornmealPizza().crustGrain 		// returns Wheat  What?!

Something is wrong: cornmeal pizza is not made from wheat! Oops, I forgot the ‘r’ in crustGain again. But with value types, there’s no override keyword to help the compiler find my mistakes. This omission seems out of place in a language otherwise designed to include enough redundancy to help find one’s errors. Without help from the compiler, we’ll have to be more careful. As a general rule,

Double-check attribute names that override protocol extensions.

Ok, let’s fix the typo:

struct CornmealPizza: Pizza {  let crustGrain: Grain = .Corn }

And try it out:

 NewYorkPizza().crustGrain 		// returns Wheat
 ChicagoPizza().crustGrain 		// returns Wheat
CornmealPizza().crustGrain 		// returns Corn  Hooray!

Pizza in a variable, and a wrong answer

In order to discuss pizza without worrying about New York, Chicago, or cornmeal, the Pizza protocol can be used as the type of a variable:

var pie: Pizza

And that variable can be used to answer questions about different pizzas:

pie =  NewYorkPizza(); pie.crustGrain  // returns Wheat
pie =  ChicagoPizza(); pie.crustGrain  // returns Wheat
pie = CornmealPizza(); pie.crustGrain  // returns Wheat    Not again?!

Why did the program lie and say that the cornmeal pizza contained wheat? The code that Swift compiled for the crustGrain query ignored the actual value of the variable. The only information that the compiler allowed the compiled code to use was what could be known when the program was compiled, not what happened when the program ran. All that can be known at compile-time is that pie is a Pizza, and the Pizza protocol extension says Wheat, so the declaration of a cornmeal crust in the CornmealPizza structure had no effect whatsoever when asking pie for something. Although the compiler could have warned about the potential for error from this use of static- instead of dynamic-dispatch, it did not. I believe that here lurks a trap for the unwary, and I would call this a major snare.

In this case, Swift provides a fix. In addition to defining the crustGrain attribute in the extension:

protocol  Pizza {}
extension Pizza {  var crustGrain: Grain { return .Wheat }  }

The code can declare the attribute in the protocol:

protocol  Pizza {  var crustGrain: Grain { get }  }
extension Pizza {  var crustGrain: Grain { return Wheat }  }

Presenting Swift with both a declaration and a definition in this fashion causes the compiler to take notice of the runtime value of the pie variable. (But not always, Had we not defined crustGrain in the extension, the declaration of crustGrain in the protocol would mean that every aggregate [structure, class, or enumeration] that inherits from Pizza must implement crustGrain.)

An attribute declaration in a protocol has two different meanings, static- vs dynamic-dispatch, depending on whether or not the attribute is defined in an extension.

With the addition of the declaration, the code works:

pie =  NewYorkPizza();  pie.crustGrain	 // returns Wheat
pie =  ChicagoPizza();  pie.crustGrain	 // returns Wheat
pie = CornmealPizza();  pie.crustGrain	 // returns Corn    Whew!

This facet of Swift is a serious snare; even after it had become clear, it continued to infest my code with bugs. Thanks to Alexandros Salazar who has a nice writeup of this issue. There is no compile-time check for this mistake (as of this writing 12/23/15, Xcode 7.2). To avoid this pitfall:

For every attribute defined in a protocol extension, declare it in the protocol itself.

However, this circumvention is not always possible…

Imported protocols cannot be fully extended

Frameworks (and libraries) let a program import interfaces to code without including all of the implementation. For example Apple provides many frameworks that implement the user experience, system facilities, and other functions. Swift’s extension facility allows a program to add its own attributes to imported classes, structures, enumerations, and protocols. For the concrete types (classes, structures, and enumerations), an attribute added via an extension works just as well as if it had been present in the original definition. But an attribute defined in a protocol extension is not such a first-class citizen of the protocol because it is impossible to add a declaration with a protocol extension.

Let’s try to import a framework defining pizzas and extend it to handle crusts. The framework defines the protocol, and the concrete types:

// PizzaFramework:

public protocol Pizza { } public struct NewYorkPizza: Pizza { public init() {} } public struct ChicagoPizza: Pizza { public init() {} } public struct CornmealPizza: Pizza { public init() {} }

and we’ll import the framework and extends pizzas with crust information:

import PizzaFramework

public enum Grain { case Wheat, Corn }

extension Pizza         { var crustGrain: Grain { return .Wheat	} }
extension CornmealPizza { var crustGrain: Grain { return .Corn	} }

Just as before, static-dispatch yields a bad answer:

var pie: Pizza = CornmealPizza()
pie.crustGrain                            // returns Wheat   Wrong!

This is because (as explained previously) the crustGrain attribute is not declared in the protocol, but only defined in the extension. However, without editing the source code in the framework, we cannot fix this problem. Hence, it is impossible to safely extend a protocol declared in another framework (without gambling that it will never need dynamic-dispatch.) To avoid this problem:

Do not extend an imported protocol with a new attribute that may need dynamic dispatch.

Attributes in restricted protocol extensions: declaration is no longer enough

When extending a generic protocol with attributes that apply to only some of the possible types, one can define them in a restricted protocol extension. But the semantics may not be what is expected.

Recall our running pizza example:

enum Grain { case Wheat, Corn }

protocol  Pizza { var crustGrain: Grain { get }  }
extension Pizza { var crustGrain: Grain { return .Wheat }  }

struct  NewYorkPizza: Pizza  { }
struct  ChicagoPizza: Pizza  { }
struct CornmealPizza: Pizza  { let crustGrain: Grain = .Corn }

Let’s make a meal out of pizza. Sadly, not all meals do include pizza, so we’ll accomodate different types of main dishes with a type parameter of a generic Meal structure:

struct Meal<MainDishOfMeal>: MealProtocol {
    let mainDish: MainDishOfMeal
}

Meal inherits from MealProtocol protocol to allow the code to test if a meal is gluten-free. We use of a protocol in order to allow the gluten-free code to be shared with meals that have other representations, for examples a meal without a main dish.

protocol MealProtocol {
    typealias MainDish_OfMealProtocol
    var mainDish: MainDish_OfMealProtocol {get}
    var isGlutenFree: Bool {get}
}

To avoid poisoning people—better safe than sorry—the code includes a conservative default:

extension MealProtocol {
    var isGlutenFree: Bool  { return false }
}

Happily, there is one dish that is OK: pizza made with corn instead of wheat. Swift’s where construction provides a way to express this case as a restricted protocol extension. When the main dish is pizza, we know it has crust, so it is safe to ask it about the crust. Without the restrictive where clause, the code would be unsafe:

extension MealProtocol  where  MainDish_OfMealProtocol: Pizza {
    var isGlutenFree: Bool  { return mainDish.crustGrain == .Corn }
}

The extension with a where is called a restricted extension.

Let’s get cooking with a nice cornmeal pizza!

let meal: Meal<Pizza> = Meal(mainDish: CornmealPizza())

And double-check our dish:

meal.isGlutenFree	// returns false
// But there is no gluten! Why can’t I have that pizza?

As shown in a previous section, a declaration in a protocol was sufficient to induce dynamic dispatch for a defined of the corresponding attribute in the protocol extension. But a definition in a restricted extension is always statically-dispatched. To prevent bugs caused by unexpected static-dispatch:

Avoid extending a protocol with a restriction if the new attribute might need dynamic dispatch.

Even if one avoids the snares related to protocol extensions, there are other problematic constructions in Swift. Most of these are mentioned in Apple’s Swift book, but since they may be more salient when isolated, we include a discussion of them below.

Optional chaining for assignment and side-effects

Swift’s optional types can prevent errors by providing static checking for the possilibity of a nil value. It provides a convenient shorthand, optional chaining, to handle cases where a nil value can be ignored, as was the default in Objective-C. Unfortunately, a detail of Swift’s semantics for optional chaining when the potentially-nil reference is the target of an assignment can result in errors. Consider an object that holds an integer, a possibly-nil pointer to it, and an assignment:

class Holder  { var x = 0 }
var n = 1
var h: Holder? = ...
h?.x = n++
n  // 1 or 2?

The final value of n depends on whether h is nil or not! If h is not nil, the assignment happens, the increment operator happens, and n ends up holding 2. But if h is nil, not only the assignment is skipped, but the increment is also skipped, and n ends up holding 1. In order to avoid surprises caused by missing side-effects,

Avoid assigning the result of an expression with side-effects to left-hand-side with optional chaining.


Functional programming snares in Swift

Swift’s support for functional programming brings the ability to apply the benefits of that paradigm to the Apple ecosystem. Swift functions and closures are first-class entities, convenient to use, with powerful abilities. Unfortunately, there are a couple of traps to avoid here.

In-out parameters silently fail with closures

A Swift in-out parameter allows a function to receive a value from a variable in the caller and then set the value of that variable. A Swift closure supports references to functions captured in mid-execution. Each of these contribute to elegant and expressive code, so you may be tempted to use them together, but the combination can be problematic.

Let’s rewrite the crustGrain attribute to illustrate an in-out parameter. We’ll start simply, without a closure:

enum Grain { case Wheat, Corn }

struct CornmealPizza {
    func setCrustGrain(inout grain: Grain)  { grain = .Corn }
}

To use the function, we pass it a variable. After the function returns, the variable’s value has been changed from Wheat to Corn.

let pizza = CornmealPizza()
var grain: Grain = .Wheat
pizza.setCrustGrain(&grain)
grain		// returns Corn

Now, let’s try a function that returns a closure that sets an grain parameter:

struct CornmealPizza {
    func getCrustGrainSetter()   ->   (inout grain: Grain) -> Void {
        return { (inout grain: Grain) in grain = .Corn }
    }
}

Using this closure merely requires on more invocation:

var grain: Grain = .Wheat
let pizza = CornmealPizza()
let aClosure = pizza.getCrustGrainSetter()
grain			// returns Wheat (We have not run the closure yet)
aClosure(grain: &grain)
grain			// returns Corn

So far so good, but what happens if we pass in the grain parameter to the closure-creator instead of the closure itself?

struct CornmealPizza {
    func getCrustGrainSetter(inout grain: Grain)  ->  () -> Void {
        return { grain = .Corn }
    }
}

And trying it:

var grain: Grain = .Wheat
let pizza = CornmealPizza()
let aClosure = pizza.getCrustGrainSetter(&grain)
grain				// returns Wheat (We have not run the closure yet)
aClosure()
grain				// returns Wheat  What?!?

In-out parameters do not work when passed into the outer scope of a closure, so

Avoid in-out parameters in closures.

This problem is mentioned in the Swift book, but there is a related issue concerning the equivalence of currying to creating a closure.

In-out parameters expose an inconsistency with currying

For a function that creates and returns a closure, Swift provides a compact syntax for the function’s type and body. Although this currying syntax is supposed to be a mere shorthand, it harbors a surprise when used with in-out parameters. To reveal the surprise, let’s try the same example with the special currying syntax: instead of declaring the function type’s as returning a function, the code includes a second parameter list after the first, and it omits the explicit closure creation:

struct CornmealPizza {
    func getCrustGrainSetterWithCurry(inout grain: Grain)() -> Void {
        grain = .Corn
    }
}

Just as in the explicit closure creation form, invoking this function returns a closure:

var grain: Grain = .Wheat
let pizza = CornmealPizza()
let aClosure = pizza.getCrustGrainSetterWithCurry(&grain)

But where the explicitly-created closure above failed to set the in-out parameter, this one succeeds!

aClosure()
grain				// returns Corn

Currying works for in-out parameters where explicit closure-creation fails.

Avoid currying with in-out parameters because the code will fail if you later change it to explicitly create a closure.


Summary

Apple’s Swift language has been carefully crafted to optimize the creation of software. As with any ambitious undertaking, a handful rough edges remain that can result in a program not working as expected. In order to avoid such unpleasant surprises, let’s review these snares so you can avoid them in your own code:

Double-check attribute names that override protocol extensions.

For every attribute defined in a protocol extension, declare it in the protocol itself.

Do not extend an imported protocol with a new attribute that may need dynamic dispatch.

Avoid extending a protocol with a restriction if the new attribute might need dynamic dispatch.

Avoid assigning the result of an expression with side-effects to left-hand-side with optional chaining.

Avoid in-out parameters in closures.

Avoid currying with in-out parameters because the code will fail if you later change it to explicitly create a closure.

David Ungar, Research Staff Member, IBM Almaden Research Center

23 comments on"Seven Swift Snares & How to Avoid Them"

  1. Great post, David. Could you please elaborate on the snare regarding optional chaining? I couldn’t find any details about “Avoid assigning the result of an expression with side-effects to left-hand-side with optional chaining.”

    • David Ungar January 28, 2016

      Thank you for catching that. There was an editing glitch and the relevant section got piped into /dev/null. We’ve restored it. I hope it explains that issue.

    • I guess this snare will be obsolete in Swift 2.2, as ++ and — were removed from the language?

      • David Ungar January 31, 2016

        Good point, but not quite. Consider h?.x = f() or even h?.x = p. Either f() or even p can have side-effects, so there is still some potential for bugs. But removing ++ and — is likely to reduce the temptation to write code that would incur these problems.

  2. Optional Chaining translates to `map` for Optional type. So
    `h?.x = n++` –> `h.map { x = n++ }`. map will execute the closure if h is not nil.

    • David Ungar January 31, 2016

      Yes, optional chaining does the same thing as rewriting to use ‘map’ as in your comment. The worry I have for Swift programmers is that ‘h?.x = n++’ tends to look like n will be incremented all the time. Suppose I start with a non-optional type for h: ‘h.x = n++’ then later change h to be optional. The simplest, fastest change would be to just add that question mark after the ‘h’. If I haven’t thought carefully about the nil case, hilarity may well ensue.

      I don’t see the same issue if I write it with the ‘map’ construction because that puts the increment inside a statement in braces. To my eye at least, the braces signal that the contained statement may or may not be executed, in a more salient fashion than the question mark on the other side of the assignment operator.

      • Completely agree! This is a great article, all your points are very important.
        I actually saw it posted on Twitter and one of the Swift engineers commented that most of these will be added to the diagnostics in the future and should present a warning.

        I didn’t explain but my point was that to show why this behavior is happening. When devs understand how the optional chaining operator works, they understand the consequences as well.

        • David Ungar January 31, 2016

          Thank you. Great that warnings are coming! Your post was a nice explanation. I suppose the compiler-writers could have made the other choice, to evaluate the right-hand-side of the assignment even if the left-hand were nil. Each choice has its pros and cons. The other choice would not be equivalent to the ‘map’ construction, and would use cycles to no effect in some cases, but it might forestall a bug or two.

          Given the present semantics, a warning may be difficult to implement if warns exactly when there are side-effects that could be transitively caused by the evaluation of the right-hand-side.

  3. Awesome post. The isGlutenFree example does not compile though.

    • David Ungar January 31, 2016

      Thank you.
      If you are referring to the following example, it seemed to compile for me. Here is the whole thing:

      import Foundation

      enum Grain { case Wheat, Corn }

      protocol Pizza { var crustGrain: Grain { get } }
      extension Pizza { var crustGrain: Grain { return .Wheat } }

      struct NewYorkPizza: Pizza { }
      struct ChicagoPizza: Pizza { }
      struct CornmealPizza: Pizza { let crustGrain: Grain = .Corn }

      struct Meal: MealProtocol {
      let mainDish: MainDishOfMeal
      }

      protocol MealProtocol {
      typealias MainDish_OfMealProtocol
      var mainDish: MainDish_OfMealProtocol {get}
      var isGlutenFree: Bool {get}
      }

      extension MealProtocol {
      var isGlutenFree: Bool { return false }
      }

      extension MealProtocol where MainDish_OfMealProtocol: Pizza {
      var isGlutenFree: Bool { return mainDish.crustGrain == .Corn }
      }

      let meal: Meal = Meal(mainDish: CornmealPizza())

      meal.isGlutenFree // returns false
      // But there is no gluten! Why can’t I have that pizza?

  4. Nice post! It makes you worry when using third party frameworks! 🙂 🙂 🙂

    • Thank you. Swift is still fairly new and evolving. I suspect that many of these snares are just temporary growing pains.

  5. Maxim Veksler February 09, 2016

    I feel this comment “Swift’s reference types (i.e. classes) have served this example well. But if this program involved concurrency, race conditions could be avoided by using Swift’s value types with their linguistic support for immutability.” was not explained enough.

    Could you please explain David, or possibly provide an example where concurrency comes into play for object declarations / initialisations?

    I understand that having Value types ensures a 1 time memory allocation and initialisation, what I’m trying to figure out is where you claim this follow breaks down for Reference types.

    Happy to learn new things,
    Tnx.

    • Thank you for asking this question. I have been working on a new post that will address an issue related to using value types to prevent concurrent access bugs. (It isn’t the next one in the pipeline for this blog, but the one after that. My posts also appear on my personal blog, at blog.davidungar.net.) I’ll do my best to make the explanation clear, but it takes some time to do so.

      In the meantime, the short answer to what I think you are asking is that the mutability of value types can lead to races between mutation and access, or mutation and mutation. I’m planning a nice pizza example for my post which I hope will make it mouth-wateringly clear.

  6. Joao Alves March 11, 2016

    Nice post David, it has helped me a lot in understanding a bit more about the usage of protocols. I just got a bit confused in one part. Where does the “MainDishOfMeal” type come from? (in let mainDish: MainDishOfMeal). Should I declare another protocol of this type? I tried to use let mainDish: MainDish_OfMealProtocol, but it does not seems to work. Once again, thanks a lot.

    • David Ungar March 11, 2016

      Thank you, Joao. Nice catch! You have found an editing glitch. The definition of
      struct Meal: MealProtocol
      should have been:
      struct Meal<MainDishOfMeal>: MealProtocol
      My apologies.

  7. sangjoon moon April 12, 2016

    Great post David. It is a good chance for me to make better understanding about protocol extensions. Thank you.
    During testing your code, I got an error and warning message (in “Attributes in restricted protocol extensions: declaration is no longer enough” section)

    1) typealias need to be “associatedtype” since Swift 2.2 later.

    2) let meal: Meal = Meal(mainDish: CornmealPizza())

    I think that it should be :

    let meal : Meal = Meal(mainDish: CornmealPizza())

    And I got an interesting result when I did not declare a type parameter for Meal.

    let meal : Meal = Meal(mainDish: CornmealPizza())
    meal.isGlutenFree // returns true

    How to explain this one? Do Swift get a type information from struct’s memberwise initializers?

    • sangjoon moon April 12, 2016

      I can’t find edit menu in my comment.
      your code is :
      let meal : Meal = Meal(mainDish: CornmealPizza())

      I think it should be :
      let meal : Meal = Meal(mainDish: CornmealPizza())

      • David Ungar April 12, 2016

        Thank you very much for checking this out. I need to make some edits to bring this post up to date; Swift has improved since I wrote it. I’m grateful to the Swift team.

        • sangjoon moon April 14, 2016

          Hi David. I wrote same code twice because the this comment system do not allow to use angle brackets and preview option.
          Thank you for your quick response and a good posting again.

  8. David Ungar April 28, 2016

    Fixed! Thank you.

  9. “For every attribute defined in a protocol extension, declare it in the protocol itself.”
    Just a thought on this: Since stored properties cannot be defined in protocol extensions, all your examples here are using computed properties. i.e. properties which have no mutable state. So technically this isn’t a snare unless you’re defining computed properties in your protocol extensions, no?

Leave a Reply