Hi everyone,
SE-0502 (Swift 6.4) improved the memberwise initializer so that less-accessible properties with initial values no longer force the whole initializer down to a lower access level. It's a great improvement, but it only helps when the excluded property happens to be less accessible than its siblings. I'd like to propose closing the remaining gap: a way to exclude a property from the synthesized initializer regardless of access level.
Motivation
SE-0502 fixes this:
struct S {
var x: Int
var y: Int
private var z = 0
}
let s = S(x: 1, y: 2) // legal since Swift 6.4
But it only works because z is less accessible than x/y.
Several common patterns don't fit that shape.
Auto-generated identity.
struct TodoItem: Identifiable {
let id: UUID = UUID()
var title: String
}
id and title are both the same access level, so id stays in the synthesized initializer:
init(id: UUID = UUID(), title: String)
A caller can now pass an arbitrary or duplicate id, silently breaking the identity guarantee Identifiable is supposed to provide. This is a very common shape for local models, cache entries, and SwiftUI list items, and today the only fix is to hand-write the whole initializer.
private(set) properties.
SE-0502 explicitly considered and declined to take setter visibility into account:
"we chose not to do this to match the existing access level behavior of the memberwise initializer, which only takes the overall access level of the property into account."
You might expect private(set) to fix a derived-value case like this:
struct Rectangle {
var width: Double
var height: Double
private(set) var area: Double = 0
}
Rectangle(width: 3, height: 4, area: 999) // still compiles
It doesn't — SE-0502 only looks at a property's own access level, not its setter's, so area stays exposed exactly as before.
The workaround is a hand-written initializer, which forfeits synthesis for every property, not just the one that needs special handling.
SE-0502 names this gap directly as a future direction it left unaddressed:
we could potentially have an attribute that you could add to a property to indicate that it should not be included in the memberwise initializer... we feel it is better explored as part of a future direction.
Proposed solution
A new attribute, @Init(.ignore), attached to a stored property, excludes it from the synthesized memberwise initializer regardless of access level:
struct TodoItem: Identifiable {
@Init(.ignore) let id: UUID = UUID()
var title: String
}
// Synthesized: init(title: String)
The property must have an initial value — same requirement SE-0502 already places on properties it excludes — since nothing else will assign it.
struct S {
@Init(.ignore) var x: Int // error: needs an initial value
}
Detailed design
- Applies to any stored property (including property-wrapper-backed ones) with an initial value, using the same definition of "initial value" as SE-0502 (explicit expression, or implicit default like
Optional.none). - Exclusion is applied before SE-0502's access-level computation, so an ignored property never affects the resulting access level and is dropped regardless of its own access level, including
public. - Scoped to the implicit memberwise initializer only. This does not add any syntax for mixing synthesized and hand-written parameters inside an explicit
init— that's the much larger problem SE-0018 attempted and didn't resolve. No new keyword, no placeholder syntax.
Source compatibility
Purely additive; nothing changes unless a property opts in.
ABI compatibility
None — the synthesized initializer is never more accessible than internal, as today.
Alternatives considered
Reviving SE-0018 in full. SE-0018 bundled this with access-control changes, class support, lazy handling, and parameter forwarding, and stalled. Keeping this proposal to property exclusion only avoids repeating that.
@nomemberwise naming. Considered, but @Init(.ignore) matches naming already validated by the community (gohanlon/swift-memberwise-init-macro, in production at Point-Free), and the @Init(...) namespace leaves room for related future options like @Init(.public) without new attributes per option.
Future directions
@Init(.public)or similar, to let apublicstruct's synthesized initializer itself bepublic— the separate "internal-only ceiling" limitation.- Custom parameter labels / default values for
letproperties (SE-0018, community macros already do this). - Extending synthesis to
classtypes.
Happy to hear thoughts, especially on the naming and whether the scope here feels right.