Swift
Struct
Memberwise Initializer
Access Control
Programming

How can I make the memberwise initialiser public, by default, for structs in Swift?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Swift automatically synthesizes a memberwise initializer for many structs, but that initializer is not automatically part of your public library API. This often surprises package authors who mark a struct as public and then discover that code in another module still cannot construct it the way they expected.

Why the Synthesized Initializer Is Not Public

Swift treats public API as something you should expose intentionally. If every synthesized memberwise initializer became public automatically, then simply adding, removing, or reordering stored properties could break downstream callers without any explicit API decision from the library author.

That is why public struct does not imply “public memberwise initializer.” The language expects you to write the public initializer yourself when cross-module construction is part of the API.

The Standard Solution Is an Explicit public init

The usual fix is to declare the initializer manually and mark it public.

swift
1public struct Car {
2    public let model: String
3    public let year: Int
4
5    public init(model: String, year: Int) {
6        self.model = model
7        self.year = year
8    }
9}

This looks like boilerplate, but it gives you precise control over the public construction surface of the type.

Manual Initializers Are Better for Invariants Anyway

Once you are writing the initializer yourself, you can use it to protect invariants instead of exposing raw construction rules mechanically.

swift
1public struct Percentage {
2    public let value: Int
3
4    public init(_ value: Int) {
5        precondition((0...100).contains(value), "value must be between 0 and 100")
6        self.value = value
7    }
8}

That is usually a better public API than a raw memberwise initializer because it constrains invalid states at the point of construction.

Organize Public Initializers Cleanly

For larger types, some teams prefer to keep stored properties in the struct declaration and group public initializers in an extension for readability.

swift
1public struct UserProfile {
2    public let id: String
3    public let displayName: String
4}
5
6public extension UserProfile {
7    init(id: String, displayName: String) {
8        self.id = id
9        self.displayName = displayName
10    }
11}

This does not change the access-control rule. It just makes the API surface easier to scan, especially when you have several convenience initializers.

Plan for API Evolution

A public initializer becomes part of your source-compatibility contract. If you later add a new stored property, you may not want to break every existing caller. One common technique is to add a defaulted parameter.

swift
1public struct CarV2 {
2    public let model: String
3    public let year: Int
4    public let trim: String
5
6    public init(model: String, year: Int, trim: String = "Standard") {
7        self.model = model
8        self.year = year
9        self.trim = trim
10    }
11}

That allows the API to evolve without immediately forcing every client to change.

Test From Another Module

Access-control issues often go unnoticed because code inside the same module can see more than a library consumer can. If this struct belongs to a framework or Swift package, include at least one test or example target that imports the module from the outside and constructs the public types there.

That is the best way to catch “looks public locally, but is not usable externally” problems before release.

What You Cannot Enable by Default

There is no global Swift setting that says “make all synthesized memberwise initializers public.” The language intentionally avoids that behavior. If you want a public construction API, you need to define it explicitly, even if the body mirrors the stored properties closely.

For internal code generation workflows you can reduce boilerplate, but at the language level the rule remains explicit.

Common Pitfalls

The biggest mistake is assuming that public struct is enough for public initialization. Another is exposing a raw initializer without thinking about invariants or future API evolution. Developers also test only inside the module and miss the fact that external code still cannot call the initializer.

Summary

  • Swift does not automatically make synthesized memberwise initializers public.
  • If external modules should construct the struct, write an explicit public init.
  • Manual initializers are also the right place to enforce invariants.
  • Plan initializer signatures with source compatibility in mind.
  • Test construction from another module to verify the API you think you exposed is actually public.

Course illustration
Course illustration

All Rights Reserved.