Articles, podcasts and news about Swift development, 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, then 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")

However, if a struct contains a private property, then 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
    }
}

The exception to that rule, though, is that when we’re using certain property wrappers (such as SwiftUI’s State wrapper), then we are able to make those wrapped properties private, while still being able to call the enclosing type’s memberwise initializer from outside of that type:

struct CounterView: View {
    var title: String
    @State private var count = 0

    var body: some View {
        VStack {
            Text("\(title): \(count)")
            Button("Increment") {
                count += 1
            }
        }
    }
}

let view = CounterView(title: "My counter")

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.