So far we've been working with Swift's built-in types โ String, Int, Bool, Array. But what if you need a type that doesn't exist yet? What if you want to represent a ninja, or an anime character, or a guild? That's exactly what structs are for. ๐ฅ
A struct lets you create your own custom, complex data type โ complete with its own variables and its own functions.
Creating Your First Struct
Here's a simple struct that represents an anime character:
struct AnimeCharacter {
let name: String
let show: String
let powerLevel: Int
func printSummary() {
print("\(name) from \(show) โ Power Level: \(powerLevel)")
}
}
Notice a few things:
-
AnimeCharacterstarts with a capital letter โ that's the Swift convention for all types (String,Int,Bool, and now our ownAnimeCharacter) -
name,show, andpowerLevelare properties โ variables and constants that belong to the struct -
printSummary()is a method โ a function that belongs to the struct
Now let's create some characters:
let naruto = AnimeCharacter(name: "Naruto", show: "Naruto Shippuden", powerLevel: 9000)
let goku = AnimeCharacter(name: "Goku", show: "Dragon Ball Z", powerLevel: 9001)
print(naruto.name) // "Naruto"
print(goku.powerLevel) // 9001
naruto.printSummary() // "Naruto from Naruto Shippuden โ Power Level: 9000"
goku.printSummary() // "Goku from Dragon Ball Z โ Power Level: 9001"
Even though both naruto and goku are created from the same AnimeCharacter struct, they are completely separate instances โ changing one won't affect the other. When printSummary() is called on naruto, it uses Naruto's data. When called on goku, it uses Goku's data. Swift handles this automatically.
Properties, Methods, and Instances โ The Vocabulary
Now that you've seen a struct in action, let's give names to the pieces:
-
Properties โ the variables and constants inside a struct (
name,show,powerLevel) -
Methods โ the functions inside a struct (
printSummary()) -
Instance โ a specific copy of a struct (
naruto,gokuare both instances ofAnimeCharacter) -
Initializer โ the special function used to create an instance (
AnimeCharacter(name:show:powerLevel:))
That last one is worth slowing down on.
How Initializers Work
When you write AnimeCharacter(name: "Naruto", show: "Naruto Shippuden", powerLevel: 9000), it looks like you're calling a function. You kind of are โ Swift silently creates a special function called init() inside every struct, using all the properties as parameters. These two lines are identical:
let naruto = AnimeCharacter(name: "Naruto", show: "Naruto Shippuden", powerLevel: 9000)
let naruto = AnimeCharacter.init(name: "Naruto", show: "Naruto Shippuden", powerLevel: 9000)
You'll almost always use the first version โ but now you know what's actually happening behind the scenes.
Swift is also smart about default values. If you give a property a default value, Swift makes it optional in the initializer:
struct Ninja {
let name: String
var chakraLevel = 100
}
let kakashi = Ninja(name: "Kakashi") // uses default chakraLevel of 100
let rock = Ninja(name: "Rock Lee", chakraLevel: 0) // Rock Lee has no chakra ๐
Mutating Properties: The mutating Keyword
Let's say we want our ninja to be able to train and increase their chakra:
struct Ninja {
let name: String
var chakraLevel: Int
func train() {
chakraLevel += 50 // โ This won't work!
print("\(name) trained hard!")
}
}
Swift will refuse to build this. Why? Because chakraLevel is a var โ it can change โ but Swift doesn't know whether the instance of the struct is a var or a let. If you created a let ninja, changing its properties would be wrong.
The solution is to mark any method that changes properties with the mutating keyword:
struct Ninja {
let name: String
var chakraLevel: Int
mutating func train() {
chakraLevel += 50
print("\(name) trained hard! Chakra is now \(chakraLevel).")
}
}
Now it works โ but only when the instance is created as a var:
var naruto = Ninja(name: "Naruto", chakraLevel: 100)
naruto.train() // โ
"Naruto trained hard! Chakra is now 150."
let sasuke = Ninja(name: "Sasuke", chakraLevel: 100)
sasuke.train() // โ Error โ can't call mutating method on a constant
Two important things to remember about mutating:
-
Swift takes your word for it โ if you mark a method as
mutating, Swift will prevent it from being called on constant structs, even if the method doesn't actually change anything -
A non-mutating method can't call a mutating one โ if you need that, mark both as
mutating
Methods vs Functions โ What's the Difference?
You might be wondering: if a method is just a function inside a struct, why bother? Why not just use functions for everything?
The key difference is that methods belong to a type โ they live inside a struct (or class, or enum). Regular functions float freely in your code.
This matters for two reasons:
1. Methods can access the struct's own properties:
struct Guild {
let name: String
var memberCount: Int
func describe() {
// Can access 'name' and 'memberCount' directly
print("\(name) has \(memberCount) members.")
}
}
A regular function outside the struct couldn't do this without being passed the data explicitly.
2. Methods avoid namespace pollution:
Imagine you write 100 free-floating functions โ attack(), defend(), heal(), etc. Those names now mean something everywhere in your code, and you might accidentally conflict with other code using the same names.
But if those functions are methods on a Character struct, they don't conflict with anything โ character.attack() is clearly about that character's attack, and item.attack() could mean something completely different on an Item struct.
Structs vs Tuples โ Which Should You Use?
You might remember tuples from the functions article โ they let you return multiple values at once:
func getCharacterInfo() -> (name: String, powerLevel: Int) {
return ("Luffy", 8500)
}
A tuple and a struct can hold similar data, so when should you use each?
Think of a tuple as an anonymous struct โ it's great for one-off situations, like returning two values from a single function. But if you find yourself passing the same shape of data around in multiple functions, a struct is the better choice:
// With tuples โ gets repetitive and hard to update:
func authenticate(_ user: (name: String, powerLevel: Int, guild: String)) { ... }
func showProfile(for user: (name: String, powerLevel: Int, guild: String)) { ... }
func signOut(_ user: (name: String, powerLevel: Int, guild: String)) { ... }
// With a struct โ clean, reusable, and easy to update:
struct Hero {
var name: String
var powerLevel: Int
var guild: String
}
func authenticate(_ hero: Hero) { ... }
func showProfile(for hero: Hero) { ... }
func signOut(_ hero: Hero) { ... }
If you ever need to add a new property โ say, var rank: String โ you only add it once to the struct, and every function automatically benefits. With tuples, you'd have to update every function signature manually.
Rule of thumb:
- Use tuples for quick, one-off multi-value returns
- Use structs when the same data shape appears in multiple places
Wrap Up
| Term | What It Means |
|---|---|
| Struct | A custom data type with its own properties and methods |
| Property | A variable or constant that belongs to a struct |
| Method | A function that belongs to a struct |
| Instance | A specific copy of a struct |
| Initializer | The function used to create an instance |
mutating |
Marks a method that changes a struct's properties |
Structs are one of the most important building blocks in Swift โ and in SwiftUI, almost everything you build will be a struct. Getting comfortable with them now will make everything that follows feel much more natural. ๐ธ
This article was written by me; AI was used to improve grammar and readability.
Top comments (0)