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.