Every time you create a struct instance โ like AnimeCharacter(name: "Naruto", powerLevel: 9000) โ you're calling an initializer. Swift generates one for you automatically, but sometimes you need more control over how your struct is set up. That's where custom initializers come in. ๐ฅ
The Default Memberwise Initializer
When you create a struct with properties, Swift automatically generates what's called a memberwise initializer โ a special function that accepts each property as a parameter, in the order they're defined:
struct Ninja {
let name: String
let village: String
let rank: Int
}
let naruto = Ninja(name: "Naruto", village: "Konoha", rank: 7)
Swift silently created Ninja(name:village:rank:) for us. We didn't have to write anything. That's convenient โ but sometimes we want to take control of that process ourselves.
Writing Your Own Initializer
Here's how you write a custom initializer that does the same thing as the default one:
struct Ninja {
let name: String
let village: String
let rank: Int
init(name: String, village: String, rank: Int) {
self.name = name
self.village = village
self.rank = rank
}
}
A few important things to notice:
-
No
funckeyword โ initializers look like functions but Swift treats them specially - No return type โ an initializer always returns an instance of the struct it belongs to
-
selfโ we useself.nameto mean "thenameproperty of this instance," as opposed to thenameparameter coming in
That last point is really important. Without self, writing name = name would be confusing โ is that assigning the property to the parameter, or the other way around? self.name = name makes it crystal clear: "assign the parameter name to my own property name."
Why Use self?
self refers to the current instance of the struct โ the specific ninja, character, or object you're working with right now.
The most common place you'll use it is in initializers, exactly as above. Without it, you'd have to use awkward parameter naming to avoid the clash:
// Without self โ clunky parameter names:
init(ninjaName: String, ninjaVillage: String, ninjaRank: Int) {
name = ninjaName
village = ninjaVillage
rank = ninjaRank
}
// With self โ clean, matching names:
init(name: String, village: String, rank: Int) {
self.name = name
self.village = village
self.rank = rank
}
The self version is much more readable. Outside of initializers, you generally don't need self โ Swift can figure out what you mean. But inside an initializer where property names and parameter names match, self is the clear, unambiguous solution.
The Golden Rule
There is one rule for initializers that Swift enforces strictly:
Every property must have a value by the time the initializer ends.
No exceptions. If you forget to assign a value to even one property, Swift will refuse to build your code.
struct Ninja {
let name: String
let village: String
let rank: Int
init(name: String, village: String) {
self.name = name
self.village = village
// โ rank was never assigned โ Swift won't build this!
}
}
You can satisfy this rule by assigning a value directly in the initializer, or by giving the property a default value when you declare it.
Custom Initializers With Different Behavior
The real power of custom initializers is that they don't have to work the same way as the default one. For example, what if every new ninja is automatically assigned a random mission rank between 1 and 10?
struct Ninja {
let name: String
let village: String
let missionRank: Int
init(name: String, village: String) {
self.name = name
self.village = village
self.missionRank = Int.random(in: 1...10) // assigned automatically!
}
}
let sakura = Ninja(name: "Sakura", village: "Konoha")
print(sakura.missionRank) // random number between 1 and 10
We provide name and village โ Swift generates missionRank for us inside the initializer. The golden rule is still satisfied because all three properties get a value before the initializer ends.
Multiple Initializers
You can have more than one initializer in a struct โ useful when you want to create instances in different ways:
struct Ninja {
let name: String
let village: String
// Create a named ninja from a specific village
init(name: String, village: String) {
self.name = name
self.village = village
}
// Create a mysterious unknown ninja
init() {
self.name = "???"
self.village = "Unknown"
}
}
let kakashi = Ninja(name: "Kakashi", village: "Konoha")
let mystery = Ninja() // name: "???", village: "Unknown"
The Catch: Custom Initializers Remove the Default One
Here's something important: as soon as you write your own initializer, Swift removes the automatically generated memberwise initializer.
struct Ninja {
let name: String
let village: String
init() {
self.name = "???"
self.village = "Unknown"
}
}
let kakashi = Ninja(name: "Kakashi", village: "Konoha") // โ No longer available!
let mystery = Ninja() // โ
Works fine
Swift does this deliberately โ if you wrote a custom initializer, it assumes that's your intended setup process, and the default one might skip important steps.
If you want to keep both the default memberwise initializer and your custom one, move the custom initializer into an extension:
struct Ninja {
let name: String
let village: String
}
extension Ninja {
init() {
self.name = "???"
self.village = "Unknown"
}
}
let kakashi = Ninja(name: "Kakashi", village: "Konoha") // โ
Still works!
let mystery = Ninja() // โ
Custom one works too!
By putting the custom initializer in an extension, Swift keeps the automatically generated memberwise initializer in place alongside it. Both are available. ๐
Wrap Up
| Concept | What It Means |
|---|---|
| Memberwise initializer | Auto-generated by Swift โ accepts each property as a parameter |
| Custom initializer | Written by you โ init() with no func keyword and no return type |
self |
Refers to the current instance โ used to distinguish property names from parameter names |
| Golden rule | Every property must have a value by the end of the initializer |
| Extension trick | Add a custom init in an extension to keep the default memberwise init too |
This article was written by me; AI was used to improve grammar and readability.
Top comments (0)