Why Choose Struct Over Class?

i0S Swift Issue

Question or problem with Swift language programming:

Playing around with Swift, coming from a Java background, why would you want to choose a Struct instead of a Class? Seems like they are the same thing, with a Struct offering less functionality. Why choose it then?

How to solve the problem:

Solution 1:

According to the very popular WWDC 2015 talk Protocol Oriented Programming in Swift (video, transcript), Swift provides a number of features that make structs better than classes in many circumstances.

Structs are preferable if they are relatively small and copiable because copying is way safer than having multiple references to the same instance as happens with classes. This is especially important when passing around a variable to many classes and/or in a multithreaded environment. If you can always send a copy of your variable to other places, you never have to worry about that other place changing the value of your variable underneath you.

With Structs, there is much less need to worry about memory leaks or multiple threads racing to access/modify a single instance of a variable. (For the more technically minded, the exception to that is when capturing a struct inside a closure because then it is actually capturing a reference to the instance unless you explicitly mark it to be copied).

Classes can also become bloated because a class can only inherit from a single superclass. That encourages us to create huge superclasses that encompass many different abilities that are only loosely related. Using protocols, especially with protocol extensions where you can provide implementations to protocols, allows you to eliminate the need for classes to achieve this sort of behavior.

The talk lays out these scenarios where classes are preferred:

Copying or comparing instances doesn’t make sense (e.g., Window)
Instance lifetime is tied to external effects (e.g., TemporaryFile)
Instances are just “sinks”–write-only conduits to external state (e.g.CGContext)

It implies that structs should be the default and classes should be a fallback.

On the other hand, The Swift Programming Language documentation is somewhat contradictory:


Structure instances are always passed by value, and class
instances are always passed by reference. This means that they are
suited to different kinds of tasks. As you consider the data
constructs and functionality that you need for a project, decide
whether each data construct should be defined as a class or as a
structure.
As a general guideline, consider creating a structure when one or more
of these conditions apply:

The structure’s primary purpose is to encapsulate a few relatively simple data values.
It is reasonable to expect that the encapsulated values will be copied rather than referenced when you assign or pass around an
instance of that structure.
Any properties stored by the structure are themselves value types, which would also be expected to be copied rather than referenced.
The structure does not need to inherit properties or behavior from another existing type.

Examples of good candidates for structures include:

The size of a geometric shape, perhaps encapsulating a width property and a height property, both of type Double.
A way to refer to ranges within a series, perhaps encapsulating a start property and a length property, both of type Int.
A point in a 3D coordinate system, perhaps encapsulating x, y and z properties, each of type Double.

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.

Here it is claiming that we should default to using classes and use structures only in specific circumstances. Ultimately, you need to understand the real world implication of value types vs. reference types and then you can make an informed decision about when to use structs or classes. Also, keep in mind that these concepts are always evolving and The Swift Programming Language documentation was written before the Protocol Oriented Programming talk was given.

Solution 2:

Since struct instances are allocated on stack, and class instances are allocated on heap, structs can sometimes be drastically faster.

However, you should always measure it yourself and decide based on your unique use case.

Consider the following example, which demonstrates 2 strategies of wrapping Int data type using struct and class. I am using 10 repeated values are to better reflect real world, where you have multiple fields.

class Int10Class {
    let value1, value2, value3, value4, value5, value6, value7, value8, value9, value10: Int
    init(_ val: Int) {
        self.value1 = val
        self.value2 = val
        self.value3 = val
        self.value4 = val
        self.value5 = val
        self.value6 = val
        self.value7 = val
        self.value8 = val
        self.value9 = val
        self.value10 = val
    }
}

struct Int10Struct {
    let value1, value2, value3, value4, value5, value6, value7, value8, value9, value10: Int
    init(_ val: Int) {
        self.value1 = val
        self.value2 = val
        self.value3 = val
        self.value4 = val
        self.value5 = val
        self.value6 = val
        self.value7 = val
        self.value8 = val
        self.value9 = val
        self.value10 = val
    }
}

func + (x: Int10Class, y: Int10Class) -> Int10Class {
    return IntClass(x.value + y.value)
}

func + (x: Int10Struct, y: Int10Struct) -> Int10Struct {
    return IntStruct(x.value + y.value)
}

Performance is measured using

// Measure Int10Class
measure("class (10 fields)") {
    var x = Int10Class(0)
    for _ in 1...10000000 {
        x = x + Int10Class(1)
    }
}

// Measure Int10Struct
measure("struct (10 fields)") {
    var y = Int10Struct(0)
    for _ in 1...10000000 {
        y = y + Int10Struct(1)
    }
}

func measure(name: String, @noescape block: () -> ()) {
    let t0 = CACurrentMediaTime()

    block()

    let dt = CACurrentMediaTime() - t0
    print("\(name) -> \(dt)")
}

Code can be found at https://github.com/knguyen2708/StructVsClassPerformance

UPDATE (27 Mar 2018):

As of Swift 4.0, Xcode 9.2, running Release build on iPhone 6S, iOS 11.2.6, Swift Compiler setting is -O -whole-module-optimization:

  • class version took 2.06 seconds
  • struct version took 4.17e-08 seconds (50,000,000 times faster)

(I no longer average multiple runs, as variances are very small, under 5%)

Note: the difference is a lot less dramatic without whole module optimization. I’d be glad if someone can point out what the flag actually does.


UPDATE (7 May 2016):

As of Swift 2.2.1, Xcode 7.3, running Release build on iPhone 6S, iOS 9.3.1, averaged over 5 runs, Swift Compiler setting is -O -whole-module-optimization:

  • class version took 2.159942142s
  • struct version took 5.83E-08s (37,000,000 times faster)

Note: as someone mentioned that in real-world scenarios, there will be likely more than 1 field in a struct, I have added tests for structs/classes with 10 fields instead of 1. Surprisingly, results don’t vary much.


ORIGINAL RESULTS (1 June 2014):

(Ran on struct/class with 1 field, not 10)

As of Swift 1.2, Xcode 6.3.2, running Release build on iPhone 5S, iOS 8.3, averaged over 5 runs

  • class version took 9.788332333s
  • struct version took 0.010532942s (900 times faster)

OLD RESULTS (from unknown time)

(Ran on struct/class with 1 field, not 10)

With release build on my MacBook Pro:

  • The class version took 1.10082 sec
  • The struct version took 0.02324 sec (50 times faster)

Solution 3:

Similarities between structs and classes.

I created gist for this with simple examples.
https://github.com/objc-swift/swift-classes-vs-structures

And differences

1. Inheritance.

structures can’t inherit in swift. If you want

class Vehicle{
}

class Car : Vehicle{
}

Go for an class.

2. Pass By

Swift structures pass by value and class instances pass by reference.

Contextual Differences

Struct constant and variables

Example (Used at WWDC 2014)

struct Point{
 
   var x = 0.0;
   var y = 0.0;

} 

Defines a struct called Point.

var point = Point(x:0.0,y:2.0)

Now if I try to change the x. Its a valid expression.

point.x = 5

But if I defined a point as constant.

let point = Point(x:0.0,y:2.0)
point.x = 5 //This will give compile time error.

In this case entire point is immutable constant.

If I used a class Point instead this is a valid expression. Because in a class immutable constant is the reference to the class itself not its instance variables (Unless those variables defined as constants)

Solution 4:

Assuming that we know Struct is a value type and Class is a reference type.

If you don’t know what a value type and a reference type are then see What’s the difference between passing by reference vs. passing by value?

Based on mikeash’s post:


… Let’s look at some extreme, obvious examples first. Integers are
obviously copyable. They should be value types. Network sockets can’t
be sensibly copied. They should be reference types. Points, as in x, y
pairs, are copyable. They should be value types. A controller that
represents a disk can’t be sensibly copied. That should be a reference
type.
Some types can be copied but it may not be something you want to
happen all the time. This suggests that they should be reference
types. For example, a button on the screen can conceptually be copied.
The copy will not be quite identical to the original. A click on the
copy will not activate the original. The copy will not occupy the same
location on the screen. If you pass the button around or put it into a
new variable you’ll probably want to refer to the original button, and
you’d only want to make a copy when it’s explicitly requested. That
means that your button type should be a reference type.
View and window controllers are a similar example. They might be
copyable, conceivably, but it’s almost never what you’d want to do.
They should be reference types.
What about model types? You might have a User type representing a user
on your system, or a Crime type representing an action taken by a
User. These are pretty copyable, so they should probably be value
types. However, you probably want updates to a User’s Crime made in
one place in your program to be visible to other parts of the program.
This suggests that your Users should be managed by some sort of user
controller which would be a reference type. e.g
struct User {}
class UserController {
var users: [User]

func add(user: User) { … }
func remove(userNamed: String) { … }
func …
}

Collections are an interesting case. These include things like arrays
and dictionaries, as well as strings. Are they copyable? Obviously. Is
copying something you want to happen easily and often? That’s less
clear.
Most languages say “no” to this and make their collections reference
types. This is true in Objective-C and Java and Python and JavaScript
and almost every other language I can think of. (One major exception
is C++ with STL collection types, but C++ is the raving lunatic of the
language world which does everything strangely.)
Swift said “yes,” which means that types like Array and Dictionary and
String are structs rather than classes. They get copied on assignment,
and on passing them as parameters. This is an entirely sensible choice
as long as the copy is cheap, which Swift tries very hard to
accomplish.

I personally don’t name my classes like that. I usually name mine UserManager instead of UserController but the idea is the same

In addition don’t use class when you have to override each and every instance of a function ie them not having any shared functionality.

So instead of having several subclasses of a class. Use several structs that conform to a protocol.


Another reasonable case for structs is when you want to do a delta/diff of your old and new model. With references types you can’t do that out of the box. With value types the mutations are not shared.

Solution 5:

Here are some other reasons to consider:

  1. structs get an automatic initializer that you don’t have to maintain in code at all.

    struct MorphProperty {
       var type : MorphPropertyValueType
       var key : String
       var value : AnyObject
    
       enum MorphPropertyValueType {
           case String, Int, Double
       }
     }
    
     var m = MorphProperty(type: .Int, key: "what", value: "blah")
    

To get this in a class, you would have to add the initializer, and maintain the intializer…

  1. Basic collection types like Array are structs. The more you use them in your own code, the more you will get used to passing by value as opposed to reference. For instance:

    func removeLast(var array:[String]) {
       array.removeLast()
       println(array) // [one, two]
    }
    
    var someArray = ["one", "two", "three"]
    removeLast(someArray)
    println(someArray) // [one, two, three]
    
  2. Apparently immutability vs. mutability is a huge topic, but a lot of smart folks think immutability — structs in this case — is preferable. Mutable vs immutable objects

Hope this helps!