Jun 3, 2026

Swift Initializers Part 1: Structs

Swift Initializers Part 1: Structs

Swift Initializers: Understanding Initializers in Structs

Introduction

Over the years, I have created a number of videos address the topic of initializers in Swift. If you are interested in those videos instead of reading this blog, you can find them here.

Everyone has their own learning style, so I thought that for those of you who prefer written content over YouTube videos, I would create an blog post and see if that works for you.

This is the first of two posts on initializers and in this one, I will focus on initializers in structs.

One of the first things you'll encounter when learning Swift is the concept of an initializer. Every instance of a struct must be initialized before it can be used. An initializer ensures that all stored properties have valid values before an instance becomes available.

We'll begin with the basics and progressively explore more advanced initialization techniques.


Why Do We Need Initializers?

When you create an instance of a struct, you need to provide values for all its stored properties.

Swift requires every stored property to have a value before an instance can exist.

For example, consider the following struct.

struct User {
    var name: String
    var isPremium: Bool
}

If we just try to create an instance of user withouth providing values for all of its stored properties, we get an error. Fortunately, as you will see shortly, Swift will do its best to prevent us from trying to do this.

let user = User() // Error

Default Property Values

When you create your struct, you can provide default values for all of the store properties and when you do this, where we failed previous, we now succeed.

struct User {
    var name: String = "Guest"
    var isPremium: Bool = false
}
let user = User() // OK

The Memberwise Initializer

Earlier, I mentioned that Swift will do its best to prevent us from trying to create an instance of a struct without providing values for all of its stored properties. This is unique to structs and does not apply to classes as you will learn in the next post. Swift automatically creates a memberwise initializer for structs that do not define their own initializer.

This means, when you start to create an instance of your struct, Xcode will automatically provide you with all of the properties that have not yet been initialized so you can provide values for them

struct User {
    var name: String
    var isPremium: Bool
}

let user = User(name: "Stewart", isPremium: true)

Memberwise Initializer

Constant Properties and Initialization

One of the primary reasons initializers exist is to provide values for constant properties.

struct User {
    let name: String
    let age: Int
}
let user = User(name: "Stewart", age: 75)

After initialization, these values cannot be changed.

user.name = "Bob" // Error

Constants make your types safer because they cannot be modified after creation. Some of the most common uses for constants are IDs and creation dates.

Memberwise Initializers and Default Values

If you have properties that do have a default value assigned, you will get additional memberwise initializes so that you can override the default value.

struct User {
    var name: String
    var isPremium: Bool
    var age = 74
}

let user = User(name: "Stewart", isPremium: true)
or
let user = User(name: "Stewart", isPremium: true, age: 75)

Memberwise2

If there is more than one property that has a default value assigned, Xcode will collapse them into a single line where the ones with default values are dimmed out so of you press enter on your keyboard you will get only those properties that do not have a value assigned and the other values will be assigned their default values.

struct User {
    var name: String
    var isPremium: Bool
    var age = 74
    var sex = "M"
}

Memberwise3

If, when creating the instance, you continue to type the names or partial names of the properties that have default values, they will be no longer be dimmed, and tapping on the keyboard will crate the memberwise initializer for all of those properties

CleanShot 2026-05-30 at 13.04.40

The other alternative is to use the right arrow key on your keyboard to present all possible initializers for the structure and you can choose which one you want.

CleanShot 2026-05-30 at 13.04.40

Custom Initializers

If memberwise initializers did not exist, as you will see in the next post for classes, you would have to create your own initializer.

For example, in our struct that we started with

struct User {
    var name: String
    var isPremium: Bool
}

The initializer, which is basically a method that requires parameters for all properties that do not have a default value and assign those parameters to the corresponding properties.

init(name: String, isPremium: Bool) {
  self.name = name
  self.isPremium = isPremium
}

This is what we will have to do with classes as you will see in the next post.

The confusing part for beginners is the use of the syntax self.name = name

self.name refers to the actual property instance, and not the parameter, so we need to avoid ambiguity.

There is no reason why we need to use the same strings for our parameter labels as our properties however. This is just convention. If we do not use the same strings, then we can avoid using self. For example the following is a perfectly legitimate initializer.

Note that when creating an instance of User, the initializer parameter labels are used.

struct User {
    var name: String
    var isPremium: Bool
    init(initialName: String, initialIsPremium: Bool) {
        name = initialName
        isPremium = initialIsPremium
    }
}

let user = User(initialName: "Stewart", initialIsPremium: true)

I am not a supporter of doing this however, because it can lead to confusion. It is likely wiser to use external parameter labels instead.


Initializer Parameter Labels

Initializer parameters support both external and internal names.

struct Video {
    let nom: String

    init(videoTitle nom: String) {
        self.nom = nom
    }
}

Usage:

let video = Video(videoTitle: "Swift Initializers")

Here:

  • videoTitle is the external label
  • nom is the internal parameter name

When an instance is created, the initializers uses the external label and then assigns whatever was provided to the property in your struct that has the internal label.

Good labels can create self-documenting code, particularly if you are dealing with an API where the property names are not obvious.

let video = Video(videoTitle: "Swift Initializers")

This may clearer to English speakers than:

let video = Video(nom: "Swift Initializers")

Omitting External Labels

Sometimes, you don't want to have to provide a label at all and you can do this by providing an _ as the external parameter label

struct Video {
    let title: String

    init(_ title: String) {
        self.title = title
    }
}
let video = Video("Swift Initializers")

Use omitted labels sparingly and only when the meaning is obvious.


Preserving the Memberwise Initializer

One of the initial drawbacks of creating a custom initializer is that you lose the memberwise version. This can be resolved however, by creating an extension to the struct, and creating your custom initializer in the extension instead, you get both

struct User {
    let name: String
    let isPremium: Bool
}

extension User {
    init(name: String) {
        self.init(name: name, isPremium: false)
    }
}

Now both the memberwise initializer and the custom initializer are available.

Memberwise6


Multiple Initializers

You can also create multiple initializers for a struct. By doing so, you can create a version of an initializer that will assign a default value for one of the properties as you see in this example, where the second initializer assigns false to the stored isPremium property.

struct User {
    let name: String
    let isPremium: Bool

    init(name: String, isPremium: Bool) {
        self.name = name
        self.isPremium = isPremium
    }

    init(name: String) {
        self.name = name
        self.isPremium = false
    }
}

You can use multiple initializers when there are multiple valid ways to create an instance.

Initializer Delegation

And there is an another way to accomplish the same thing as above by using initializer delegation. Delegation can avoid duplication by having one initializer call another

For example, here, the second initializer

struct User {
    let name: String
    let isPremium: Bool

    init(name: String, isPremium: Bool) {
        self.name = name
        self.isPremium = isPremium
    }

    init(name: String) {
        self.init(name: name, isPremium: false)
    }
}

Convenience Initializer for Structs

There is a simpler way to accomplish exactly the same thing as the above two example. You can provide a default value right within the initializer itself

struct User {
    let name: String
    let isPremium: Bool

    init(name: String, isPremium: Bool = false) {
        self.name = name
        self.isPremium = isPremium
    }
}

Both

Convenience


Failable Initializers

Initializers can be failable too. You add ? to the end of of the init.

struct EmailAddress {
    let value: String

    init?(value: String) {
        guard value.contains("@") else {
            return nil
        }

        self.value = value
    }
}
let valid = EmailAddress(value: "stewart@example.com") // valid Optional<EmailAddress>
let invalid = EmailAddress(value: "invalid") // nil

Use init? when initialization should either succeed or fail.


Throwing Initializers

Initializers can also throw errors and this can be handy if you want to provide detailed failure information. These are better in my opinion than Failable initializers.

enum PasswordError: Error {
    case tooShort
    case missingNumber
}

struct Password {
    let value: String

    init(value: String) throws {
        guard value.count >= 8 else {
            throw PasswordError.tooShort
        }

        guard value.contains(where: \.isNumber) else {
            throw PasswordError.missingNumber
        }

        self.value = value
    }
}

Final Example

Putting it all together, here is an example of a Course struct that

  • Default parameter values
  • Validation
  • Throwing initialization
  • Delegating initialization
enum CourseError: Error {
    case emptyTitle
    case invalidLessonCount
}

struct Course {
    let title: String
    let lessonCount: Int
    let isPublished: Bool

    init(
        title: String,
        lessonCount: Int,
        isPublished: Bool = false
    ) throws {

        guard !title.isEmpty else {
            throw CourseError.emptyTitle
        }

        guard lessonCount > 0 else {
            throw CourseError.invalidLessonCount
        }

        self.title = title
        self.lessonCount = lessonCount
        self.isPublished = isPublished
    }

    init(draftTitle: String) throws {
        try self.init(
            title: draftTitle,
            lessonCount: 1,
            isPublished: false
        )
    }
}

Conclusion

Most structs begin life with default values or Swift's synthesized memberwise initializer. As your types become more sophisticated, custom initializers allow you to validate input, simplify APIs, and prevent invalid state.

A good progression is:

  1. Start with the memberwise initializer.
  2. Add default values where appropriate.
  3. Create custom initializers when business rules emerge.
  4. Delegate whenever possible.
  5. Use failable or throwing initializers when validation is required.

Understanding these patterns will make your Swift code safer, clearer, and easier to maintain.

------

YouTube videos on Initializers

Here are some videos that I have created on YouTube on Initializers.

The first two were back in 2020

Initializers Part I

https://youtu.be/qtR13ygYEns)

Initializers Part II

https://youtu.be/We4rLsgKAws

And then I created another one in 2024 with much of the same content, but presented in a slightly different way.

Understanding Swift Initializers

https://youtu.be/ElfPQZ9MVTQ

Return to introduction