Friday, July 3, 2015

Should I Use a Swift Struct or a Class?

Drew Crawford:

A lobby that wants you to believe that one weird trick will make your codebase better, replacing the cargo-cult Agile practices with equivalently useless cargo-cult functionalist practices.

[…]

As we’ve established, most types (say, 90%) can implement funcs in a non-mutating way. But to quote another Zawinski law, “Everybody uses a different 10%”. […] Similarly, for every type that is sufficiently large, it conforms to some non-mutating protocol with a mutating implementation. […] Now. How are we going to solve this problem? Well, the mutating inner class.

[…]

It is not wrong to wrap a class in a struct. This is how Swift.Array works under the hood. […] The difference is, A) they have been really clever about hiding the inner class, which involves a lot of code and neat tricks that you can learn more about in the WWDC session (e.g., this is an advanced topic, that you should not sprinkle liberally throughout your codebase where the muggles will see it) and B) there is nothing fundamentally classful about arrays. Arrays are, from a certain point of view, a kind of value. So it makes sense for them to be a value type.

[…]

The moral of this story is that immutable, pure functional programming does not always work even in the literal textbook examples. A mutating implementation of the Sieve is both simpler and faster, and far from helping, functional programming has actually hindered the exercise of writing fast, maintainable code.

[…]

Finally, the official Apple guidance concludes with this dire warning:

In all other cases, define a class, and create instances of that class to be managed and passed by reference. In practice, this means that most custom data constructs should be classes, not structures.

Update (2015-07-05): David Owens II:

The fundamental problem I find with the piece is that it creates a false selection of choices to the problem statement posed while misrepresenting much of the advice about choosing when to use value types. I think it also confuses the purpose of the proposed “inner class device”.

[…]

The entire purpose of the “inner class” is to provide value semantics while maintaining efficiency of implementation. In addition to that, if some of the quoted advice in the article had been followed with regards to struct usage, a different conclusion would have presented itself.

Drew Crawford:

I agree with you that inner mutability “feels” wrong in the CanSend example. But the suggestion that inner mutability should never ever be used is wrong, because of Swift.Array. So we need some rule to distinguish when inner-mutability-with-structs is bad from when it is okay. If the rule was “never do any mutation inside a struct” then we would not have value-typed-arrays to begin with.

(In fact, we didn’t have value-typed-arrays to begin with. I have a serious theory that it was exactly the rigid application of “never have inner mutation” inside Swift that initially led to the silly behavior. Recall that in early betas, the value/reference behavior of arrays used to hinge on whether or not the operation caused a resize--which is exactly those cases in which the array implementation needed to mutate the free block chain.)

David Owens II:

I think it’s safe to say that using inner classes to simply break the mutability contract of a protocol is a poor reason that’s going to lead to all sorts of bad behavior (like the original arrays in Swift).

[…]

I’m saying that I think that line is somewhere near the area of, “I really want to present a value-semantic type, but the performance is going to suck if I do, so let’s create an implementation with a backing inner class”.

Update (2015-07-22): Mike Ash:

The answer is actually really simple: use structs when you need value semantics, and use classes when you need reference semantics. That’s it!

[…]

Thus the fundamental question to ask when deciding which one to use is: does it make sense to copy this type? Is copying an operation you want to make easy, and use often?

2 Comments RSS · Twitter

There are good ideas in there and lots of food for thought, but I find the way he presents the supposed strong 'functionalist' stand of others completely misguided. Reading his post, I did not find his arguments different from what I had read or heard before from Andy Matuschak et al. Everybody is basically saying that Objective C everything-is-a-class approach is not the way to write Swift, and one should really think about value types vs object types. Yes, one can err too much on either side, but none of the posts he links to are recommending a 'pure' functional approach or that everything should be a struct.

I completely agree with charles’s comment. There is a good argument to be made here, but this essay is not it. And I found the needlessly combative tone of the author to be a huge turn-off.

Leave a Comment