Structs

VeronicaRoth

Becoming fearless isn't the point. That's impossible. It's learning how to control your fear, and how to be free from it. -Veronica Roth

Overview

In this lesson. we'll cover the basics about Swift's structs.

Learning Objectives

  • Create and describe the syntax of structs
  • Distinguish structs from classes
  • Explain the difference between reference and value types when assigning each to variables and constants
  • Change a struct's properties

Structs

In the last few lessons, you've learned how you can use classes to make your own data types in Swift, complete with their own properties and behaviors (known formally as methods). As a reminder, take a look at how you might create a class to represent a Person. It would probably look something like this:

class Person {
    let firstName: String
    let lastName: String
    var fullName: String {
        return "\(firstName) \(lastName)"
    }

    init(firstName: String, lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
    }

    func goForARun() {
        print("I love running!")
    }
}

You can create instances of that class pretty easily:

let jim = Person(firstName: "Jimbo", lastName: "Guiseppe")
print(jim.fullName)
// prints "Jimbo Guiseppe"
jim.goForARun()
// prints "I love running!"

In Swift, structs are very similar. They are data types that have properties, methods, and initializers. In fact, you declare them almost the same as you do a class, except you use the struct keyword instead of class. Here's how you could define a Person struct:

struct Person {
    let firstName: String
    let lastName: String
    var fullName: String {
        return "\(firstName) \(lastName)"
    }

    func goForARun() {
        print("I love running!")
    }
}

And here's how you could create an instance of your Person struct:

let jim2 = Person(firstName: "Jimbo", lastName: "Guiseppe")
print(jim.fullName)
// prints "Jimbo Guiseppe"
jim.goForARun()
// prints "I love running!"

Not much different from a class, huh? The biggest difference you may have noticed is that the Person struct does not have an initializer. Unlike classes, you don't have to define an initializer for a struct. If you don't define one, a default one will be created that simply takes all stored (not computed) properties as parameters, and assigns those parameters to the appropriate property. You can define your own initializer, though, if you want it to do more than that—just like a class.

So why use a struct instead of a class? Are default initializers the only benefit structs have?

Nope! Structs behave a bit differently than classes. Classes are reference types, whereas structs are value types.

Wait, reference types? Value types? What does that gobbledygook mean? Well, it's probably best to illustrate with an example.

First, let's go back to our example of a Person class. This time, though, make the properties variables, so that you can change them after you have initialized the class:

class Person {
    var firstName: String
    var lastName: String
    var fullName: String {
        return "\(firstName) \(lastName)"
    }

    init(firstName: String, lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
    }
}

Now, create an instance of this Person called person1, then create a variable called person2 and assign person1 to it, like this:

var person1 = Person(firstName: "Luke", lastName: "Skywalker")
var person2 = person1

Now try printing out their full names:

print(person1.fullName)
// prints "Luke Skywalker"
print(person2.fullName)
// prints "Luke Skywalker"

"Luke Skywalker" is printed twice. Pretty much what you expected, right? Now, try changing person1's firstName and lastName, and then printing the fullNames of both person1 and person2 again:

person1.firstName = "Han"
person1.lastName = "Solo"
print(person1.fullName)
// prints "Han Solo"
print(person2.fullName)
// prints "Han Solo"

"Han Solo" is printed twice! Is this what you expected?

When you instantiate an object, that instance is stored once in memory. person1 points to that instance in memory. When you assign person1 to person2, person2 points to that same instance! Which means that if you change a property in person1, that change will be reflected in person2—because they are pointing to the same object!

Think of it like a house. If I tell you where I live, I can say, "I live at 292 West 10th Street." I can also tell you, "I live in the blue house on the corner of West 10th Street and 13th Avenue." It's the same house! I'm just describing the location in two ways. If I paint my house red, then both places will be a red house.

Structs are a bit different. Structs are value types, so when you assign the value of one struct to another variable (or constant), it gets copied in memory, and each variable (or constant) point to different instances of that struct. Let's see this in action by first creating a Person struct again. This one will also have variable properties.

struct Person {
    var firstName: String
    var lastName: String
    var fullName: String {
        return "\(firstName) \(lastName)"
    }
}

Now, repeat the previous exercise by creating a new instance of this Person struct, then assign it to another variable, then print out the full names of each variable:

var hero1 = Person(firstName: "Luke", lastName: "Skywalker")
var hero2 = hero1
print(hero1.fullName)
// prints "Luke Skywalker"
print(hero2.fullName)
// prints "Luke Skywalker"

Once again, you'll see "Luke Skywalker" printed to the console twice.

Now, change the first name of hero1 to "Han" and the last name of hero1 to "Solo", and print each variable's full name again. What do you expect to see in the console this time?

hero1.firstName = "Han"
hero1.lastName = "Solo"
print(hero1.fullName)
// prints "Han Solo"
print(hero2.fullName)
// prints "Luke Skywalker"

This time, you see both "Han Solo" and "Luke Skywalker" printed to the console. Why? Well, hero1 and hero2 both point to different copies of the Person struct. Changing hero1's properties does not affect the properties of hero2, because they are different instances!

Coffee Talk

Let's talk about coffee for a second. Here's a pretty simple class for representing a mug of coffee:

class Mug {
    var amountOfCoffee: Double = 0.0
}

An empty mug of coffee is pretty useless, so here's a function to fill it up:

func fillMug(mug: Mug) {
    mug.amountOfCoffee = 10.0
}

And now some code to fill 'er up:

let myMug = Mug()
print(myMug.amountOfCoffee)
// prints "0.0"
fillMug(myMug)
print(myMug.amountOfCoffee)
// prints "10.0"

As you probably expected, the code above first prints "0.0" to the console, then "10.0".

But wait... myMug is a constant, so how can you change the property? When it comes to classes, myMug being constant simply means you can't assign myMug to another instance—but you can still change its properties, because the Mug class is a reference type. In other words, you can't change what myMug points to, but you can change its contents.

Let's try to do the same thing with a struct. First, a simple Mug struct, and its accompanying fillMug() function:

struct Mug {
    var amountOfCoffee: Double = 0.0
}

func fillMug(mug: Mug) {
    mug.amountOfCoffee = 10.0
}

Wait...that doesn't even compile! If you try to write that code, you'll get an error stating that "'mug' is a 'let' constant."

Mug is a let constant

What's going on? Remember how structs are value types, so changing the value of a property doesn't affect other copies of that struct? When you pass a struct into a function, that function receives a copy of the struct. Changing that copy's properties won't affect anything outside of that function (it's a copy, after all), so Swift won't even bother to let you do that.

In order to change the properties of a struct, you need to make fillMug() a method. You could modify the Mug struct to look like this:

struct Mug {
    var amountOfCoffee: Double = 0.0

    func fillMug() {
        amountOfCoffee = 10.0
    }
}

Oy...we're still getting an error here, though.

self is immutable

"Self is immutable"? What does that mean?

In case you missed it before, structs are value types. Changes in properties are not reflected on copies of that struct, so by default Swift doesn't even allow instances of a struct to modify its own properties.

So what, you're just hosed? Doomed to live a caffeine-free life?

Not exactly. You can change properties within a struct's methods if you mark them with the mutating keyword:

struct Mug {
    var amountOfCoffee: Double = 0.0

    mutating func fillMug() {
        amountOfCoffee = 10.0
    }
}

Now you're free to fill up your mug:

var myMug1 = Mug()
print(myMug1.amountOfCoffee)
// prints "0.0"
myMug1.fillMug()
print(myMug1.amountOfCoffee)
// prints "10.0"

What if you create a Mug constant?

let myMug2 = Mug()
myMug2.fillMug()

Nope! You can't do that.

Constant Mug

Unlike classes, a constant struct's properties cannot be changed—not from outside the struct, not even from within the struct's own methods, even if they're marked as mutating. Once a struct is constant, it is constant. It can't change.

Structs aren't the only value types in Swift: Enums are value types, too. You'll actually encounter value types quite a bit. Many of Swift's core data structures, including strings, arrays, and dictionaries, are structs, too.

As you become more accustomed to Swift, you'll start to figure out when you should use classes and when you should use structs. For right now, it's generally easier to favor structs over classes. As you grow as a Swift programmer, though, you'll find many uses for both.

View this lesson on Learn.co

View Swift Structs Reading on Learn.co and start learning to code for free.