Quick reference
MemberwiseInit includes three macros:
@MemberwiseInit
Attach to a struct to automatically provide it with a memberwise initializer.
@MemberwiseInit
Provide an internal memberwiseinit
.@MemberwiseInit(.public)
Provide a memberwiseinit
at the provided access level. Valid access levels:.private
,.fileprivate
,.internal
,.package
,.public
,.open
.
@Init
Attach to the property declarations of a struct that @MemberwiseInit
is providing an init
for.
@Init
Include a property that would otherwise be ignored, e.g., attributed properties such as SwiftUI’s@State
properties.@Init(.ignore)
Ignore that member property. The access level of an ignored property won’t cause the macro to fail, and the property won’t be included in theinit
. Note: Ignored properties must be initialized elsewhere.@Init(.public)
For the providedinit
, consider the property as having a different access level than its declared access level. Valid access levels:.private
,.fileprivate
,.internal
,.package
,.public
,.open
.@Init(default: 42)
Specifies a default parameter value for the property’sinit
argument, necessary for defaultinglet
properties.@Init(escaping: true)
To avoid compiler errors when a property’sinit
argument can’t automatically be@escaped
, e.g. when a property’s type uses a typealias that represents a closure.@Init(label: String)
Assigns a custom parameter label in the providedinit
.- Use
@Init(label: "_")
to make theinit
parameter label-less. - Diagnostic errors arise from invalid labels, when misapplied to declarations having multiple bindings, or from naming conflicts among properties included in the
init
. (Ignored properties don’t cause conflicts.)
- Use
@Init(.public, default: { true }, escaping: true, label: "where")
All arguments can be combined.
@InitWrapper(type:)
@InitWrapper(type: Binding<String>)
Apply this attribute to properties that are wrapped by a property wrapper and require direct initialization using the property wrapper’s type.
@MemberwiseInit
struct CounterView: View {
@InitWrapper(type: Binding<Bool>)
@Binding var isOn: Bool
var body: some View { … }
}
Note The above
@InitWrapper
is functionally equivalent to the following@InitRaw
configuration:
@InitRaw(assignee: "self._isOn", type: Binding<Bool>)
.
Etcetera
@InitRaw
Attach to property declarations to directly configure MemberwiseInit.
public macro InitRaw(
_ accessLevel: AccessLevelConfig? = nil,
assignee: String? = nil,
default: Any? = nil,
escaping: Bool? = nil,
label: String? = nil,
type: Any.Type? = nil
)
@MemberwiseInit(_optionalsDefaultNil: true)
(experimental)
When set totrue
, give all optional properties a defaultinit
parameter value ofnil
. For non-public initializers, optionalvar
properties default tonil
unless this parameter is explicitly set tofalse
.@MemberwiseInit(_deunderscoreParameters: true)
(experimental)
Drop underscore prefix from generatedinit
parameter names, unless doing so would result in a naming conflict. Ignored properties won’t contribute to conflicts, and overridable using@Init(label:)
.@MemberwiseInit
onactor
,class
(experimental)
Attachable to actor and class.
Features and limitations
Custom init
parameter labels
To control the naming of parameters in the provided initializer, use @Init(label: String)
. Tip: For a label-less/wildcard parameter, use @Init(label: "_")
.
Explanation
Customize your initializer parameter labels with @Init(label: String)
:
- Label-less/wildcard parameters
@MemberwiseInit
struct Point2D {
@Init(label: "_") let x: Int
@Init(label: "_") let y: Int
}
Yields:
init(
_ x: Int,
_ y: Int
) {
self.x = x
self.y = y
}
- Custom parameter labels
@MemberwiseInit
struct Receipt {
@Init(label: "for") let item: String
}
Yields:
init(
for item: String // 👈
) {
self.item = item
}
Infer type from property initialization expressions
Explicit type annotations are not required when properties are initialized with an expression whose syntax implies type information, e.g., most Swift literals:
@MemberwiseInit
struct Example {
var count = 0 // 👈 `Int` is inferred
}
Default values, even for let
properties
Use @Init(default: Any)
to set default parameter values in the initializer. This is particularly useful for let
properties, which otherwise cannot be defaulted after declaration. For var
properties, consider using a declaration initializer (e.g., var number = 0
) as a best practice.
Explanation
MemberwiseInit, like Swift, utilizes variable initializers to assign default values to var
properties:
@MemberwiseInit
struct UserSettings {
var theme = "Light"
var notificationsEnabled = true
}
This yields:
internal init(
theme: String = "Light",
notificationsEnabled: Bool = true
) {
self.theme = theme
self.notificationsEnabled = notificationsEnabled
}
For let
properties, @Init(default:)
enables setting default values in the initializer:
@MemberwiseInit
struct ButtonStyle {
@Init(default: Color.blue) let backgroundColor: Color
@Init(default: Font.system(size: 16)) let font: Font
}
This yields:
internal init(
backgroundColor: Color = Color.blue,
font: Font = Font.system(size: 16)
) {
self.backgroundColor = backgroundColor
self.font = font
}
Explicitly ignore properties
Use @Init(.ignore)
to exclude a property from MemberwiseInit’s initializer; ensure ignored properties are otherwise initialized to avoid compiler errors.
Explanation
The @Init(.ignore)
attribute excludes properties from the initializer, potentially allowing MemberwiseInit to produce a more accessible initializer for the remaining properties.
For example:
@MemberwiseInit(.public)
public struct Person {
public let name: String
@Init(.ignore) private var age: Int? = nil // 👈 Ignored and given a default value
}
By marking age
as ignored, MemberwiseInit creates a public initializer without the age
parameter:
public init(
name: String
) {
self.name = name
}
If age
weren’t marked as ignored, MemberwiseInit would fail to compile and provide a diagnostic.
Note In line with Swift’s memberwise initializer, MemberwiseInit automatically ignores
let
properties with assigned default values, as reassigning such properties within the initializer would be invalid.
Attributed properties are ignored by default, but includable
If MemberwiseInit ignores an attributed property and causes a compiler error, you have two immediate remedies:
- Assign a default value to the property.
- Explicitly include the property in the initializer using the
@Init
annotation.
Explanation
Unlike the compiler’s default behavior, MemberwiseInit takes a more cautious approach when dealing with member properties that have attributes attached.
For a SwiftUI-based illustration, let’s look at a view without MemberwiseInit:
import SwiftUI
struct MyView: View {
@State var isOn: Bool
var body: some View { … }
}
Swift provides the following internal memberwise init
:
internal init(
isOn: Bool
) {
self.isOn = isOn
}
However, initializing @State
properties in this manner is a common pitfall in SwiftUI. The isOn
state is only assigned upon the initial rendering of the view, and this assignment doesn’t occur on subsequent renders. To safeguard against this, MemberwiseInit defaults to ignoring attributed properties:
import SwiftUI
@MemberwiseInit(.internal) // 👈
struct MyView: View {
@State var isOn: Bool
var body: some View { … }
}
This leads MemberwiseInit to provided the following initializer:
internal init() {
} // 🛑 Compiler error:↵
// Return from initializer without initializing all stored properties
From here, you have two alternatives:
- Assign a default value
Defaulting the property to a value makes the providedinit
valid, as the providedinit
no longer needs to initialize the property.
import SwiftUI
@MemberwiseInit(. internal)
struct MyView: View {
@State var isOn: Bool = false // 👈 Default value provided
var body: some View { … }
}
The resulting init
is:
internal init() {
} // 🎉 No error, all stored properties are initialized
- Use
@Init
annotation
If you understand the behavior the attribute imparts, you can explicitly mark the property with@Init
to include it in the initializer.
import SwiftUI
@MemberwiseInit(.internal)
struct MyView: View {
@Init @State var isOn: Bool // 👈 `@Init`
var body: some View { … }
}
This yields:
internal init(
isOn: Bool
) {
self.isOn = isOn
}
Automatic @escaping
for closure types (usually)
MemberwiseInit automatically marks closures in initializer parameters as @escaping
. If using a typealias for a closure, explicitly annotate the property with @Init(escaping: true)
.
Explanation
Swift Macros operate at the syntax level and don’t inherently understand type information. MemberwiseInit will add @escaping
for closure types, provided that the closure type is directly declared as part of the property. Fortunately, this is the typical scenario.
In contrast, Swift’s memberwise initializer has the advantage of working with type information. This allows it to recognize and add @escaping
even when the closure type is “obscured” within a typealias.
Consider the following struct:
public struct TaskRunner {
public let onCompletion: () -> Void
}
Through observation (or by delving into the compiler’s source code), we can see that Swift automatically provides the following internal init
:
internal init(
onCompletion: @escaping () -> Void // 🎉 `@escaping` automatically
) {
self.onCompletion = onCompletion
}
Now, with MemberwiseInit:
@MemberwiseInit // 👈
public struct TaskRunner {
public let onCompletion: () -> Void
}
we get the same init
, which we can inspect using Xcode’s “Expand Macro” command:
internal init(
onCompletion: @escaping () -> Void // 🎉 `@escaping` automatically
) {
self.onCompletion = onCompletion
}
And we can have MemberwiseInit provide a public init
:
@MemberwiseInit(.public) // 👈 `.public`
public struct TaskRunner {
public let onCompletion: () -> Void
}
This yields:
public init( // 🎉 `public`
onCompletion: @escaping () -> Void
) {
self.onCompletion = onCompletion
}
Now, suppose the type of onCompletion
got more complex and we decided to extract a typealias:
public typealias CompletionHandler = @Sendable () -> Void
@MemberwiseInit(.public)
public struct TaskRunner: Sendable {
public let onCompletion: CompletionHandler
}
Because Swift Macros don’t inherently understand type information, MemberwiseInit cannot “see” that CompletionHandler
represents a closure type that needs to be marked @escaping
. This leads to a compiler error:
public init(
onCompletion: CompletionHandler // 👈 Missing `@escaping`!
) {
self.onCompletion = onCompletion // 🛑 Compiler error:↵
// Assigning non-escaping parameter 'onCompletion' to an @escaping closure
}
To address this, when using a typealias for closures, you must explicitly mark the property with @Init(escaping: true)
:
public typealias CompletionHandler = @Sendable () -> Void
@MemberwiseInit(.public)
public struct TaskRunner: Sendable {
@Init(escaping: true) public let onCompletion: CompletionHandler // 👈
}
which results in the following valid and inspectable public init
:
public init(
onCompletion: @escaping CompletionHandler // 🎉 Correctly `@escaping`
) {
self.onCompletion = onCompletion
}
Experimental: Deunderscore parameter names
Note Prefer using
@Init(label:)
at the property level to explicitly specify non-underscored names—@MemberwiseInit(_deunderscoreParmeters:)
may be deprecated soon.
Set @MemberwiseInit(_deunderscoreParmeters: true)
to strip the underscore prefix from properties when generating initializer parameter names. If you wish to maintain the underscore or provide a custom label on a particular property, use @Init(label: String)
.
If the removal of the underscore would lead to a naming conflict among the properties included in the initializer, MemberwiseInit will not strip the underscore. (Ignored properties won’t contribute to conflicts.)
Explanation
In Swift, properties prefixed with an underscore are conventionally used as internal storage or backing properties. Setting _deunderscoreParameters: true
respects this convention, producing initializer parameter names that omit the underscore:
@MemberwiseInit(.public, _deunderscoreParmeters: true)
public struct Review {
@Init(.public) private let _rating: Int
public var rating: String {
String(repeating: "⭐️", count: self._rating)
}
}
This yields:
public init(
rating: Int // 👈 Non-underscored parameter
) {
self._rating = rating
}
To override the deunderscore behavior at the property level, use @Init(label: String)
:
@MemberwiseInit(.public, _deunderscoreParameters: true)
public struct Review {
@Init(.public, label: "_rating") private let _rating: Int
}
This yields:
public init(
_rating: Int // 👈 Underscored parameter
) {
self._rating = _rating
}
Experimental: Defaulting optionals to nil
Use @MemberwiseInit(_optionalsDefaultNil: Bool)
to explicitly control whether optional properties are defaulted to nil
in the provided initializer:
- Set
_optionalsDefaultNil: true
to default all optional properties tonil
, trading off compile-time guidance. - Set
_optionalsDefaultNil: false
to ensure that MemberwiseInit never defaults optional properties tonil
.
The default behavior of MemberwiseInit regarding optional properties aligns with Swift’s memberwise initializer:
- For non-public initializers,
var
optional properties automatically default tonil
. - For public initializers, MemberwiseInit follows Swift’s cautious approach to public APIs by requiring all parameters explicitly, including optionals, unless
_optionalsDefaultNil
is set totrue
. let
optional properties are never automatically defaulted tonil
. Setting_optionalsDefaultNil
totrue
is the only way to cause them to default tonil
.
Note Use
@Init(default:)
to generally specify default values — it’s a safer, more explicit alternative to_optionalsDefaultNil
.
Explanation
With _optionalsDefaultNil
, you gain control over a default behavior of Swift’s memberwise init. And, it allows you to explicitly opt-in to your public initializer defaulting optional properties to nil
.
Easing instantiation is the primary purpose of _optionalsDefaultNil
, and is especially useful when your types mirror a loosely structured external dependency, e.g. Codable
structs that mirror HTTP APIs. However, _optionalsDefaultNil
has a drawback: when properties change, the compiler won’t flag outdated instantiations, risking unintended nil
assignments and potential runtime errors.
In Swift:
var
property declarations that include an initial value naturally lead to default memberwiseinit
parameter values in both Swift’s and MemberwiseInit’s initializers.let
properties assigned a value at declaration become immutable, so they can’t be leveraged to specify defaultinit
parameter values.
For instance, var
property declarations can be initialized to nil
:
@MemberwiseInit(.public)
public struct User {
public var name: String? = nil // 👈
}
_ = User() // 'name' defaults to 'nil'
Yields:
public init(
name: String? = nil // 👈
) {
self.name = name
}
This isn’t feasible for let
properties:
@MemberwiseInit(.public)
public struct User {
public let name: String? = nil // ✋ 'name' is 'nil' forever
}
Where appriopriate, _optionalsDefaultNil
can be a convenient way to default optional properties to nil
in the generated initializer:
@MemberwiseInit(.public, _optionalsDefaultNil: true)
public struct User: Codable {
public let id: Int
public let name: String?
public let email: String?
public let address: String?
}
Yields:
public init(
id: Int,
name: String? = nil,
email: String? = nil,
address: String? = nil
) {
self.id = id
self.name = name
self.email = email
self.address = address
}