You already saw how value types can benefit your code by being more predictable. What you didn't see is one of the struct's most convenient features: default initializers. You saw that when we declare the Pet
struct, we provided our own init
method. For classes, this is required; for structs, it's not. If you create a struct without an initializer, it will generate its own. The default initializer for a struct is generated based on its members. Let's see what this looks like for the Pet
struct:
struct Pet { let name: String } let cat = Pet(name: "Bubbles")
The default initializer that is generated takes all of the struct's properties that don't have a default value. This is a very convenient way to create your structs. When you're challenged with the task of deciding whether you should use a struct or an enum, there are three basic rules of thumb that you can follow to determine whether you should at least try to use a struct instead of a class.
One of the most popular things that people declare as structs are models. Models tend to be fairly static; most of their data doesn't have to change, and when it does change, you want that change to be predictable and contained to one instance only so you can prevent race conditions. Whenever you define a model, try to start off with a struct. If you can't accomplish your goals using a struct, switch to a class.
One thing these structs can't do that classes can is inheriting from a superclass. Structs enforce a very flat hierarchy of objects, which is actually a good thing. Less inheritance makes your code easier to reason about. Another upside is that you can't accidentally run into conflicts with superclass definitions of properties or methods.
However, if you're creating view controllers or other objects that need to be inherited from the UIKit
classes, you will need to use a class. Whenever this isn't what you're doing, using a struct might be worth a shot.
A struct cannot inherit from other structs or classes. Note that this does not mean that a struct cannot conform to a protocol. Conforming to protocols is still possible if you use a struct.
If you're creating an object that isn't going to be very likely to change, a struct is a great choice. Due to the stricter nature of structs as we saw before, declaring an instance of a struct as a constant makes it and its members immutable. This means that you can't accidentally change the value of a struct's property if you didn't explicitly plan to do so. If this is the behavior you desire, you should try using a struct instead of a class because this will greatly improve your ability to manage mutation.
One important note to make here is that whenever a struct has a reference type as one of its properties, the reference type might still mutate. Consider the following example:
class Food { var name: String init(name: String) { self.name = name } } struct Pet { let name: String let food: Food } let cat = Pet(name: "Bubbles", food: Food(name: "Chicken")) cat.food.name = "Turkey" print(cat.food.name) // Turkey
This example demonstrates that even though cat
is declared as a constant and food
of the cat
property is a constant, we can still mutate it. This is due to the fact that Food
is a reference type, and therefore, its properties can be mutated, even if it's contained in a value type.
The preceding three rules of thumb can be used as a guideline to determine whether or not you should or could use a struct. Of course, there are other reasons why you would or wouldn't use a struct, but it's considered a best practice to use it whenever possible. The main reason for this is the way it handles immutability. As you saw, structs are a lot stricter about mutability, and many programmers consider this a good thing because it makes their code more predictable and safer.