DEV Community

Gamya
Gamya

Posted on

Swift Structs โ€” Building Your Own Custom Types ๐Ÿ—๏ธ

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)")
    }
}
Enter fullscreen mode Exit fullscreen mode

Notice a few things:

  • AnimeCharacter starts with a capital letter โ€” that's the Swift convention for all types (String, Int, Bool, and now our own AnimeCharacter)
  • name, show, and powerLevel are 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"
Enter fullscreen mode Exit fullscreen mode

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, goku are both instances of AnimeCharacter)
  • 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)
Enter fullscreen mode Exit fullscreen mode

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 ๐Ÿ˜„
Enter fullscreen mode Exit fullscreen mode

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!")
    }
}
Enter fullscreen mode Exit fullscreen mode

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).")
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Two important things to remember about mutating:

  1. 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
  2. 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.")
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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) { ... }
Enter fullscreen mode Exit fullscreen mode

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)