Weekly Swift articles, podcasts and tips by John Sundell.

When can a struct’s memberwise initializer be used?

Published on 17 Mar 2020

In Swift, types defined as structs automatically get a default initializer synthesized by the compiler — a so-called “memberwise initializer”, as the compiler will generate it based on the given struct’s members (that is, its stored properties).

For example, if we’ve defined a User struct that has a name and a preferences property, we can use its memberwise initialize to create instances simply by passing values for those two properties:

struct User {
    var name: String
    var preferences: Preferences
}

let user = User(name: "John", preferences: Preferences())

Computed properties, on the other hand, are completely ignored when the compiler synthesizes a memberwise initializer — so even if we add one, we can still keep using the above initializer just like before:

struct User {
    var name: String
    var preferences: Preferences
    var icon: Icon { .user }
}

let user = User(name: "John", preferences: Preferences())

As of Swift 5.1, memberwise initializers also take default property values into account — meaning that if we give our preferences property a default value, we’ll be able to create a User instance by just passing a name:

struct User {
    var name: String
    var preferences = Preferences()
}

let user = User(name: "John")

One cool thing is that we can keep using a type’s memberwise initializer even if that type has private properties — as long as those properties have a default value, like this:

struct User {
    var name: String
    private var preferences = Preferences()
}

let user = User(name: "John")

However, if a private property doesn’t have a default value, we’ll have to write that type’s initializer manually — in order to be able to inject a value for that property from the outside:

struct User {
    var name: String
    private var preferences: Preferences

    init(name: String, preferences: Preferences = .init()) {
        self.name = name
        self.preferences = preferences
    }
}

One thing to keep in mind, though, is that memberwise initializers will never have an access level higher than internal, which means that we can only use them internally within the module in which their type is defined.

That might initially seem like a strange restriction, but it does have merits, as we should arguably always design explicit APIs for public consumption — without making them tied to the internal structure of our data.

So, to sum up, we can use a struct’s memberwise initializer when:

All other cases require us to manually implement an initializer, at least for now.