Struct vs classes in Swift: The differences explained

Swift brings us classes and structs which both can look quite similar. When should you use a struct and when should you go for a class?

Cannot assign to property: function call returns immutable value

You’re probably not the first to just simply fallback to changing your type to a class when an error like the above occurs. Structs tend to be harder to work with while it should be easy. Hopefully, after reading this blog post, you’ll be able to work with structs more easily!

What is a class in Swift?

A class in Swift is a reference type which can contain:

  • properties
  • methods
  • subscripts
  • initializers
  • protocol conformances
  • extensions

It’s often described as a template definition of an object, like in the following Article instance definition:

class ArticleClass {
    let title: String
    let url: URL
    var readCount: Int = 0

    init(title: String, url: URL) {
        self.title = title
        self.url = url
    }
}

What is a struct in Swift?

A struct in Swift is a value type which, just like classes, can contain:

  • properties
  • methods
  • subscripts
  • initializers
  • protocol conformances
  • extensions

It can also be seen as a template definition of an object, like in the following Article instance definition:

struct ArticleStruct {
    let title: String
    let url: URL
    var readCount: Int = 0
}

What are the differences between a struct and a class?

Yes, you’re right, the above examples looked almost the same! That’s the whole issue with structs and classes, they’re pretty similar. Still, there’s quite a lot of important differences to be aware of.

Value vs reference types

One of the most important differences is that a struct is a value type while a class is a reference type. Although this could be a blog post on its own, a short explanation should be enough to understand this difference.

References to a class instance share single data which means that any changes in that class will be available to each reference. This is shown in the following code example:

let articleClass = ArticleClass(title: "Struct vs Class", url: URL(string: "www.avanderlee.com")!)
let articleClassCopy = articleClass

articleClass.readCount = 10
print(articleClassCopy.readCount) // Prints: 10

A struct is a value type and will create a unique copy for each new reference. Taking the above code example shows this important difference as the read count is only updated on the referencing instance:

var articleStruct = ArticleStruct(title: "Struct vs Class", url: URL(string: "www.avanderlee.com")!, readCount: 0)
var articleStructCopy = articleStruct

articleStruct.readCount = 10
print(articleStructCopy.readCount) // Prints: 0

The benefit of mutation in safety

With this, structs have the benefit of mutation in safety as you can trust that no other part of your app is changing the data at the same time. This makes it easier to reason about your code and is especially helpful in multi-threaded environments where a different thread could alter your data at the same time. This could create nasty bugs which are hard to debug.

In the absence of mutation, both classes, and structs act exactly the same and the benefit of mutation does no longer count.

Structs and constants

Another related difference to value types is the use of constants. If you were sharp you could see that the articleStruct was defined as a variable instead of a let constant like we did with the articleClass. The following error probably looks familiar to you:

Struct vs classes and the differences with constants
Struct vs classes and the differences with constants

A struct can only be mutated if it’s defined as a variable and it will only update the referencing instance.

Structs get an initializer for free

If you go back and compare the above code examples you can see that the ArticleClass has a defined initializer which is required for classes. Structs, however, get an initializer for free.

This gets even better with SE-242 which is implemented in Swift 5.1 and adds memberwise initializers for structs. This means the following difference:

// Before Swift 5.1 Memberwise initializers:
// Generated memberwise init: init(title: String, url: URL, readCount: Int)
let article = ArticleStruct(title: "", url: URL(string: "")!, readCount: 0)

// After Swift 5.1 Memberwise initializers, using the default 0 for read count
// Generated memberwise init: init(title: String, url: URL, readCount: Int = 0)
let article = ArticleStruct(title: "", url: URL(string: "")!)

Classes allow inheritance

Classes can inherit the characteristics of another class and with that, act like abstract classes. A common example is a custom view controller which inherit from UIViewController.

With protocols in Swift, this is often no longer needed and replaceable with protocols. Protocols can be used with both classes and structs while inheritance is only possible with classes.

Classes can be deinitialized

A class allows executing code just before it gets destroyed by using a deinit method. When you define the same deinit method in a struct you’ll get the following error:

Deinitializers may only be declared within a class

So, when should I go for a struct and when for a class?

The Swift documentation describes the decision as followed:

The additional capabilities that classes support come at the cost of increased complexity. As a general guideline, prefer structures because they’re easier to reason about, and use classes when they’re appropriate or necessary. In practice, this means most of the custom data types you define will be structures and enumerations.

This explains most of the topics we discussed above. Also, when working with Cocoa classes, you’re often required to subclass from NSObject which requires you to use a class.

A simple bullet point list will make it a lot easier to decide.
You should use a class when:

  • Comparing instance identity is needed by using ===
  • Shared mutable state is required
  • Objective-C interoperability is required

You should use a struct when:

  • Comparing instance data is needed by using ==
  • Unique copies with an independent state are required
  • The data is used in multiple threads

Any golden tips you can give?

Yes, I can! Try to go for a struct by default. Structs make your code easier to reason about and make it easier to work in multithreaded environments which we often have while developing in Swift.

Also, if you do decide to go for a class, consider to mark it as final and help the compiler by telling it that there are no other classes that inherit from your defined class.

Conclusion

Hopefully, you’ll be able to easier reason about choosing between a class or a struct. It’s definitely not always easy and possible to go for a struct but it should be considered the default when programming in Swift. Once you’ll use structs more often I’m pretty sure you’ll get used to working with them.

If you find yourself struggling often with decisions, you might be interested in the following posts as well:

Thanks!