Swift’s synthesised member wise initialiser is one of those features you rarely think about—until it stops working. For years, adding a single private stored property, even one with a default value, caused the synthesized initializer to become private, forcing developers to write boilerplate initializers just to preserve the original API. Swift 6.4 changes that with SE-0502, making memberwise initializer synthesis smarter while preserving Swift’s access control model.
What is a Memberwise Initializer?
- When you create a struct, Swift automatically writes an init for all its stored properties.
- You get this for free — no code required.
- It has been around since Swift 1.0.
struct User {
var name: String
var age: Int
}
// Swift auto-generates this behind the scenes:
// init(name: String, age: Int)
let user = User(name: "Sarah", age: 28) // ✅ Works
Default Values Make It Even Nicer
- If a property has a default value, Swift adds it as a default argument in the generated init.
- This means callers can skip providing that value if the default is good enough.
- Introduced in Swift 5.1 via SE-0242.
struct User {
var name: String
var age: Int
var isActive: Bool = true
}
// Swift generates:
// init(name: String, age: Int, isActive: Bool = true)
let user = User(name: "Sarah", age: 28) // ✅ isActive defaults to true
The Problem: One private Property Broke Everything
Here’s where things went wrong. Make that isActive property private:
struct User {
var name: String
var age: Int
private var isActive: Bool = true
}
- You would still expect User(name: “Sarah”, age: 28) to work.
- But before Swift 6.4, it doesn’t.
- Swift included isActive in the memberwise init — and because isActive is private, the entire init became private too.
What Swift actually generated:
// ❌ The whole init is now private!
private init(name: String, age: Int, isActive: Bool = true)
Trying to create a User from outside the struct gives this error:
❌ error: 'User' initializer is inaccessible
due to 'private' protection level
One internal flag, with a default value, took down your entire public-facing initializer.
The Old Workaround: Write It Yourself
The only fix was to manually write the init you expected in the first place:
struct User {
var name: String
var age: Int
private var isActive: Bool = true
// Had to write this manually 😩
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
- This works, but it’s pure boilerplate.
- It doesn’t do anything interesting — it just copies two values.
- Thousands of Swift projects have code exactly like this sitting in them right now.
The Fix in Swift 6.4: SE-0502
Swift 6.4 ships SE-0502: Exclude private initialized properties from memberwise initializer.
The new rule:
A property is excluded from the memberwise initializer when it is less accessible than the initializer would be, and it already has an initial value.
Both conditions must be true. If they are, the property is simply left out — it keeps its default value, stays private, and callers never need to know it exists.
struct User {
var name: String
var age: Int
private var isActive: Bool = true
}
// Swift 6.4 now generates:
// ✅ init(name: String, age: Int)
let user = User(name: "Sarah", age: 28) // ✅ Works, no manual init needed
What Counts as “Having an Initial Value”?
Not just explicit values — Swift is smarter than that:
Explicit default:
private var score: Int = 0 // ✅ excluded
private var tags: [String] = [] // ✅ excluded
private var label: String = "none" // ✅ excluded
Implicit default (Optionals start as nil):
private var nickname: String? // ✅ excluded — nil is the implicit default
private var cachedData: Data? // ✅ excluded
No default — still included:
private let id: UUID // ❌ still in the init, no default exists
The Rule That Still Applies: No Default = Still Included
If a private property has no default value, Swift still has to include it in the init — because there’s no value to fall back on.
struct User {
var name: String
private let id: UUID // no default value
}
// Still generates a private init:
// private init(name: String, id: UUID)
In this case, you still need to write your own init to hide id from callers:
struct User {
var name: String
private let id: UUID
init(name: String) {
self.name = name
self.id = UUID() // you decide how id is set
}
}
This makes total sense — Swift can’t make up a value for id, so it has to ask for one.
Two Initializers Now Exist Behind the Scenes
Conceptually, Swift 6.4 creates two versions of the memberwise init for the same struct:
struct User {
var name: String
var age: Int
private var isActive: Bool = true
}
Who uses it What it looks like Outside the struct init(name: String, age: Int) Inside the struct init(name: String, age: Int, isActive: Bool)
Try it in Xcode:
- Type User( from outside the struct → autocomplete shows only name and age
- Type Self( from inside the struct → autocomplete shows all three, including isActive
What About fileprivate?
- The same rule applies to fileprivate properties.
- If a fileprivate property has a default value and is less accessible than the init, it gets excluded too.
- Only properties that are already at internal or above participate normally.
Public Structs — Nothing Changed Here
This improvement does not give you a free public initializer. That rule hasn’t changed:
public struct User {
public var name: String
public var age: Int
private var isActive: Bool = true
}
- Code inside your own module can now create User(name:age:) — the private property no longer blocks that.
- But consumers of your Swift package or framework still cannot create a User unless you write an explicit public init.
- This is intentional — your public API should always be deliberate.
public struct User {
public var name: String
public var age: Int
private var isActive: Bool = true
public init(name: String, age: Int) {
self.name = name
self.age = age
}
}
Why This Also Matters for Macros
- Macros often add hidden private storage to a type behind the scenes.
- Something like private var _backing: Storage = .init().
- Before Swift 6.4, that generated property could silently make the struct’s memberwise init private — breaking code the developer never touched.
- Now, as long as the generated property has a default value, it stays out of the way.
- Macro authors can safely attach hidden state without unexpected side effects for users.
Source Compatibility Note
- Swift 6.4 keeps a compatibility overload for the old behavior temporarily.
- Existing code that passes private properties through the old synthesized init won’t immediately break.
- But a future language mode may remove this — so if your code relies on it, write an explicit private init now.
// If you do this internally, make it explicit instead of relying on synthesis:
func resetUser() -> User {
User(name: name, age: age, isActive: false) // ⚠️ won't work in future
}
// Better — write an explicit private init:
private init(name: String, age: Int, isActive: Bool) { ... }
Quick Summary
Scenario Before Swift 6.4 Swift 6.4 private var x = 0 in a struct Init becomes private x is excluded, init stays internal private var x: String? Init becomes private x is excluded (nil is default) private let x: UUID (no default) Init becomes private Still private — no default exists public struct No public init Still no public init — must be explicit
The Takeaway
- private properties with default values are implementation details — callers shouldn’t care about them.
- Swift 6.4 finally treats them that way, keeping them out of the synthesized memberwise init.
- You can now delete a lot of manual boilerplate inits that existed only to work around this limitation.
- The best language improvements aren’t always new syntax — sometimes they’re the ones that let you quietly delete code.