How to declare a memberwise initializer in a Swift extension
Table of Contents
Swift automatically creates a memberwise initializer for a struct when we don't declare one ourselves.
struct Post {
var title: String
var body: String
}
let post = Post(title: "Hello", body: "Hello, World!")
This synthesized initializer is convenient, but it has one important limitation: its access level can never be higher than internal.
When we want to expose the initializer as public, we have to write it ourselves.
Today, Swift requires this initializer to be inside the struct declaration.
public struct Post {
public var title: String
public var body: String
public init(title: String, body: String) {
self.title = title
self.body = body
}
}
SE-0546: Same-file memberwise initializer extensions proposes another place where we can declare it: an extension in the same file.
Note: SE-0546 is under review from August 22 through September 4, 2026. The behavior described in this article may change before the proposal is accepted.
The problem with memberwise initializers in extensions
I like to use extensions to organize code. For example, we can keep stored properties in the main declaration and move initializers or protocol conformances into separate extensions.
But we can't do that with a memberwise initializer today.
public struct Post {
public var title: String
public var body: String
}
extension Post {
// Invalid redeclaration of synthesized memberwise 'init(title:body:)'
public init(title: String, body: String) {
self.title = title
self.body = body
}
}
Even though we don't see it in the source code, Swift has already synthesized init(title:body:) for Post.
The initializer in the extension has the same signature, so the compiler treats it as a redeclaration.
You can easily support sarunw.com by checking out this sponsor.
Offline Transcription: Fast, privacy-focus way to transcribe audio, video, and podcast files. No data leaves your Mac.
Same-file memberwise initializer extensions
SE-0546 proposes allowing a handwritten memberwise initializer in an unconstrained extension in the same file.
public struct Post {
public var title: String
public var body: String
}
extension Post {
public init(title: String, body: String) {
self.title = title
self.body = body
}
}
In this example, the initializer in the extension suppresses the synthesized memberwise initializer. In other words, Swift uses our implementation instead of generating another one.
This lets us change the access level to public while keeping the main struct declaration focused on its stored properties.
When does an initializer suppress the synthesized initializer
An initializer in the extension suppresses the synthesized memberwise initializer when its parameters match all of the following:
- Argument labels.
- Argument types.
- Argument order.
The match must be exact.
For example, this initializer matches the one Swift would synthesize.
struct Post {
var title: String
var body: String
}
extension Post {
// Suppresses init(title:body:)
init(title: String, body: String) {
self.title = title
self.body = body
}
}
Changing the argument order creates a different initializer, so it doesn't suppress the synthesized one.
extension Post {
// A new overload
init(body: String, title: String) {
self.title = title
self.body = body
}
}
In this case, we get both initializers.
Post(title: "Hello", body: "Hello, World!")
Post(body: "Hello, World!", title: "Hello")
Special cases
The proposal is still in review, and several special cases are being discussed in the review thread.
Here is the proposed behavior at the time of writing.
Failable and throwing initializers
A matching failable initializer (init?) or throwing initializer (init throws) suppresses the synthesized initializer.
Async initializers
A matching async initializer doesn't suppress the synthesized initializer. Swift can overload an initializer based on async, so both initializers remain available.
Constrained extensions
An initializer in a constrained extension doesn't suppress the synthesized initializer.
struct Box<Value> {
var value: Value
}
extension Box where Value == String {
init(value: String) {
self.value = value
}
}
The constraint means the handwritten initializer isn't available for every Box, so Swift keeps the synthesized memberwise initializer.
Default parameter values
Default parameter values don't affect whether the memberwise initializer is suppressed.
But a memberwise initializer whose parameters all have default values doesn't suppress a separately synthesized init().
struct Counter {
var count = 1
}
extension Counter {
init(count: Int = 0) {
self.count = count
}
}
Counter().count // 1
In this example, Counter() uses the synthesized empty initializer, so count is 1.
Why must the extension be in the same file
If an extension in any file could suppress an initializer, we would have to search the entire module to understand which initializers a struct has.
Limiting this behavior to the same file makes the result easier to reason about. Swift uses a similar same-file restriction when synthesizing conformances such as Codable and Hashable in extensions.
Why this is useful for macros
This change is also important for macros that generate structs.
A macro can generate a public struct, but the memberwise initializer synthesized for that struct is still internal. We can't replace it with a public initializer in an extension because that is currently considered a redeclaration.
SE-0546 would give us a way to expose or customize that initializer after the type is generated.
It would also let a macro-generated public type conform to a protocol whose initializer requirement matches the synthesized memberwise initializer.
You can easily support sarunw.com by checking out this sponsor.
Offline Transcription: Fast, privacy-focus way to transcribe audio, video, and podcast files. No data leaves your Mac.
Summary
SE-0546 proposes allowing a struct's memberwise initializer to be declared in an extension when:
- The extension is in the same file as the struct.
- The extension is unconstrained.
- The initializer has the same argument labels, types, and order as the synthesized initializer.
When all conditions match, the handwritten initializer suppresses the synthesized one.
This is a small change, but it gives us more freedom to organize our code and solves a real limitation for public and macro-generated structs.
Read more article about Swift, Swift Evolution, or see all available topic
Enjoy the read?
If you enjoy this article, you can subscribe to the weekly newsletter.
Every Friday, you'll get a quick recap of all articles and tips posted on this site. No strings attached. Unsubscribe anytime.
Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.
If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.
Become a patron Buy me a coffee Tweet ShareHow to fix "The frame rate of your app video preview is too high" error
App Store Connect rejects app preview videos that are recorded at 60 fps. Learn why this happens and how to convert your video to 30 fps.