Blog

Indirect Struct Properties in Swift

I’m continuing to listen to the WWDC videos and think about how they apply to my own code, and recently I’ve been thinking about the intersection of three Swift topics:

In the optimization video, Michael Gottesman points out that Swift’s copy-on-write semantics for structs can cause performance problems if the struct contains many reference-counted properties. (14:12) The first time a struct is modified it must be copied, which means that the reference counts of all reference properties must be incremented. His solution is to group the reference properties into a wrapper class and store an instance of the wrapper class in the struct. That way only one reference count needs to be incremented when the struct is copied. In the value types talk, Bill Dudney shows how to implement this workaround efficiently for Swift types using the isUniquelyReferencedNonObjC function. (32:25)

This solves the performance problem, but at a cost in legibility and maintenance. For example, take this simple struct with three reference properties:

struct Customer {
    var firstName: String
    var middleName: String
    var lastName: String
}

If you apply the wrapper class optimization to this struct, you get something like this:

struct Customer {
    private class Wrapper {
        var firstName = ""
        var middleName = ""
        var lastName = ""
        
        func copy() -> Wrapper {
            let clone = Wrapper()
            clone.firstName = firstName
            clone.middleName = middleName
            clone.lastName = lastName
            return clone
        }
    }
    
    private var _values = Wrapper()
    
    private var pathForReading: Wrapper {
        get {
            return _values
        }
    }
    
    private var pathForWriting: Wrapper {
        mutating get {
            if !isUniquelyReferencedNonObjC(&_values) {
                _values = _values.copy()
            }
            return _values
        }
    }
    
    var firstName: String {
        get {
            return pathForReading.firstName
        }
        set {
            pathForWriting.firstName = newValue
        }
    }
    
    var middleName: String {
        get {
            return pathForReading.middleName
        }
        set {
            pathForWriting.middleName = newValue
        }
    }
    
    var lastName: String {
        get {
            return pathForReading.lastName
        }
        set {
            pathForWriting.lastName = newValue
        }
    }
}

This is a lot of code to write and maintain for a simple (and presumably common) optimization. And although Swift can catch some common errors in this code, there are many that it can’t catch. For instance, while the compiler will complain if you use pathForWriting in a get method, it won’t say a word if you use pathForReading in a set method, which is arguably a much bigger problem. And of course it will have no idea if you make a copy/paste error and return pathForReading.firstName from the accessor for middleName.

A Proposed Improvement

If these wrapper classes are going to be common in production Swift code, then perhaps it would be better for Swift to provide built-in support for them. I was trying to figure out what keyword might be used to request this optimization when I remembered Chris Lattner’s talk where he reveals the (currently unimplemented) indirect keyword for creating recursive cases in enums.

To me, this seems like the right keyword for requesting indirect properties in structs as well.

From a certain point of view, enums and structs in Swift aren’t that different from one another. They are both value types that can contain sub-values. (The sub-values are known as associated values for enums, and properties for structs.) The main difference is that while an enum contains one of the sub-values, a struct contains all of the sub-values.

The indirect keyword being added to enums in Swift 2.0 solves a practical, rather than a conceptual, problem. Conceptually, there is no issue with an enum containing itself as an associated value, since a program can only create a finite number of enums and eventually the recursion must end. But in practice, associated values are stored directly in the enum and so indirection is needed to avoid allocating an infinitely large object.

The use of the indirect keyword that I’m proposing for structs is also to solve a practical problem — in this case, a performance problem. But the effect is essentially the same as with enums: the sub-values are moved out to a separate object and referenced indirectly. 

If the indirect keyword were allowed in structs as well as enums, then the above code could simply become:

struct Customer {
    indirect var firstName = ""
    indirect var middleName = ""
    indirect var lastName = ""
}

The Swift compiler could gather all of the indirect properties into a wrapper class and take care of creating the hidden wrapper class, the hidden value property, and rewriting the getters and setters to indirect through the hidden property.

I’d love to hear what others think of this proposal. If you’d like make a comment, get in touch @samalone64 and I’ll update this post with the best feedback.

Combining Swift Protocols

In his article Swift Protocols Question, Brent Simmons asks why the following Swift code won’t compile:

protocol Value: Equatable {
}

protocol Smashable {
    func valueBySmashing​OtherValue​(value: Value) -> Value
}

struct Foo: Value, Smashable {
    func valueBySmashing​OtherValue​(value: Value) -> Value {
        return Bar()
    }
}

func == (a: Foo, b: Foo) -> Bool {
    return true
}

struct Bar: Value {
}

func == (a: Bar, b: Bar) -> Bool {
    return true
}

The error message says Protocol ‘Value’ can only be used as a generic constraint because it has Self or associated type requirements. This is a little misleading, because it implies that valueBySmashingOtherValue needs to be rewritten as a generic function, but that actually just pushes the problem down to the adopters of the Value protocol. The real fix is to move the use of the Equatable protocol from the Value protocol to the individual struct declarations, like this:

protocol Value {
}

protocol Smashable {
    func valueBySmashing​OtherValue​(value: Value) -> Value
}

struct Foo: Value, Smashable, Equatable {
    func valueBySmashing​OtherValue​(value: Value) -> Value {
        return Bar()
    }
}

func == (a: Foo, b: Foo) -> Bool {
    return true
}

struct Bar: Value, Equatable {
}

func == (a: Bar, b: Bar) -> Bool {
    return true
}

So why aren’t these two examples equivalent, and why does moving the use of Equatable fix the error?

When you adopt the Equatable protocol, you are promising that two instances of that type can be compared using ==. By adopting the Equatable protocol in the Value protocol, you are promising that any two Value instances can be compared using ==. Since Value is a protocol rather than a class or struct, this is an extraordinary promise to make. You are promising that Foo and Bar and any other types that adopt your Value protocol can be compared, without even knowing if they will be classes or structs or what their implementation details may be.

By moving the Equatable protocol down to the individual struct declarations, we are making the promise much simpler. Instances of Foo can be compared, and instances of Bar can be compared, but there is no promise that a Foo can be compared with a Bar.

But what if you need to compare Foo and Bar objects to each other? In this case you can’t use Equatable, since that’s not what Equatable is designed to do. Because Equatable has a Self requirement, it can only be used to test objects of the same type for equality. To test objects of different types for equality you need to define your own protocol. In this case, it makes sense to make this a part of the Value protocol.

protocol Value {
    func isEqualTo(other: Value) -> Bool
}

protocol Smashable {
    func valueBySmashing​OtherValue​(value: Value) -> Value
}

struct Foo: Value, Smashable, Equatable {
    func valueBySmashing​OtherValue​(value: Value) -> Value {
        return Bar()
    }
    
    func isEqualTo(other: Value) -> Bool {
        if let o = other as? Foo { return self == o }
        return false
    }
}

func == (a: Foo, b: Foo) -> Bool {
    return true
}

struct Bar: Value, Equatable {
    func isEqualTo(other: Value) -> Bool {
        if let o = other as? Bar { return self == o }
        return false
    }
}

func == (a: Bar, b: Bar) -> Bool {
    return true
}

This was covered in the WWDC session Protocol-Oriented Programming in Swift, starting around 37:44 in the video. At 40:05 they also show how to use the new protocol extensions feature in Swift 2 to eliminate the redundant defintions of isEqualTo:

protocol Value {
    func isEqualTo(other: Value) -> Bool
}

extension Value where Self: Equatable {
    func isEqualTo(other: Value) -> Bool {
        if let o = other as? Self { return self == o }
        return false
    }
}

protocol Smashable {
    func valueBySmashing​OtherValue​(value: Value) -> Value
}

struct Foo: Value, Smashable, Equatable {
    func valueBySmashing​OtherValue​(value: Value) -> Value {
        return Bar()
    }
}

func == (a: Foo, b: Foo) -> Bool {
    return true
}

struct Bar: Value, Equatable {
}

func == (a: Bar, b: Bar) -> Bool {
    return true
}

A Protocol-oriented Philosophy

One of the differences between a protocol-oriented language like Swift and an object-oriented language like Objective-C is how they think about code composition. Because most object-oriented languages only allow single inheritance, the class hierarchy is the primary means of sharing code and the tendency is to create deep class trees. In a protocol-0riented language, multiple protocols can be adopted by the same class, and so the tendency is to create shallow hierarchies where classes and structs adopt the particular protocols that apply to them.

Swift 2 takes this strategy even farther by allowing protocols to include code through protocol extensions, which means that the mix-and-match nature of protocols can be used not just to combine interfaces, but implementations as well.

When you are learning to think in Swift, it pays to assume that protocols should be tight and independent, and that structs and classes will adopt as many protocols as apply to them. This limits the interdependence between your modules, while still allowing a large amount of code re-use.

Subscribe to Blog