I've had to spend considerable effort working around it without requiring non-compiler-generated conformance on the enclosing types; there's a lot of JSON out there that uses "missing" as a space/bandwidth-saving synonym for explicit null
. It's why IkigaJSON has NilValueDecodingStrategy
, and why Fluent's handling of Codable
has ugly workarounds involving superDecoder(forKey:)
.
+1
So for using this proposal already; it takes an existing concept and breaks it apart into composable pieces. That decomposition lets us more generally approach problems and does not special case property wrappers. The spelling settled on seems quite reasonable and feels natural with the surrounding components.
This definitely addresses the problem scope well and lets other systems build on top of a more generalized system.
I have read the review threads, the proposal and used the feature in the development of another feature (Observation).
I feel like this will open up some new designs, but those will be considerably cleaner than the alternatives. Primarily this seems to me like an absolute MUST for macros that interact with types and generate fields.
Taking off my review manager hat for a moment, I have a few thoughts/questions:
-
I agree with @DevAndArtist's comment from the pitch thread:
- I would not force
newValue
bacause that's not a setter where the value changes. Personally I would prefervalue
orinitialValue
to make it clearer.
- I would not force
-
I still don't like the restriction that the ability to synthesize a memberwise init depends on the relative ordering of properties and their
accesses
clauses. Sinceaccesses
clauses can only refer to stored properties there's no chance of cycle, and IMO we should synthesize a memberwise init with the parameters matching source order but with properties initialized in dependency order (ties broken by source order). -
To Jordan's point above:
jrose:
Ah, I see, this is specifically for things like the dictionary-backed properties where skipping them should still be considered an error.
is there a reason that we must support this (init accessors which don't actually initialize anything)? It doesn't seem necessary for subsuming property wrappers, so I'm curious if we just ought to leave it out. It seems somewhat esoteric. But if there's a super compelling use case I'd love to hear it! (E.g., is this support necessary for
Observable
?)
ETA:
- Does invoking the setter of a stored property in an
init
accessor (can you do this?) invoke thedidSet
observer? IIRC the current rule is that setters called insideinit
never calldidSet
, seems like we should extend that toinit
accessors.

Also, a nice simplification might be to have the ability to bundle the init and set accessor so for less boilerplate where init/set have the same logic:
struct Angle { var degrees: Double var radians: Double { get { degrees * .pi / 180 } set init initializes(degrees) { degrees = newValue * 180 / .pi } } ... }
I noted myself as weakly against this direction in the pitch, but I think one thing to consider with the new syntax that puts the initializes
and accesses
clauses outside the init
clause, a future direction which allows combining the init
and set
accessors is IMO significantly more awkward (it's not clear that initializes
really only applies to the init
and not the set
). I guess you could just learn this, but it's something to consider...

Does invoking the setter of a stored property in an
init
accessor (can you do this?) invoke thedidSet
observer
No, we can't invoke the didSet
observer, because observers have access to all of self
Yeah, I'm thinking of cases where it would be (dynamically) valid to call didSet
, similar to:
struct S {
var x: Int {
didSet {
print("didSet")
}
}
var y: Int
init() {
self.x = 0
self.y = 0
self.x = 0
self.f()
}
mutating func f() {
self.x = 0
}
}
S() // only prints `didSet` once

How use-sensitive is this? If the
init
accessor is used from one@inlinable init
, diagnostics may start appearing in the body of theinit
accessor that weren't there before, correct? What if theinit
accessor is used from oneinit
, but it's anotherinit
that's inlinable?
I was imagining that you need to write @inlinable
on the init
accessor explicitly in order to use it from an @inlinable
initializer, but I can see how the proposal is totally not clear about that!

Can the
initialValue
be implied, just likenewValue
is withset
accessor
The implied parameter is called newValue
, which is deliberately consistent with set
. The reason to use newValue
instead of initialValue
is because the word "new" still fits with initialization; you are indeed providing a new value to the field when you are initializing it. I also wanted to avoid adding another magic parameter name that people need to discover and then remember. I don't really see a strong motivation to introduce initialValue
instead of newValue
here.

How do these initializers interact with
Codable
, especially compiler-generatedCodable
conformance?
init
accessors don't interact with synthesized conformances at all. Those conformances still look specifically at the set of stored properties. It's plausible that the generated conformance could use the same strategy that member-wise initializers have in this proposal, but that would be a semantic change to the way property wrappers behave today. However, this feature might allow you to write Codable
conformance synthesis through a macro that has the behavior you're after, where the conformance is based on the "original" stored properties as written (e.g. before any property wrapper or macro transformations happen).

Since the
init
to the computed variable is so special, could we just put this part in brackets like below and leave the body of the computed variable untouched?
I don't think init
accessors are any more special than get
and set
accessors.

- is there a reason that we must support this (init accessors which don't actually initialize anything)? It doesn't seem necessary for subsuming property wrappers, so I'm curious if we just ought to leave it out. It seems somewhat esoteric. But if there's a super compelling use case I'd love to hear it! (E.g., is this support necessary for
Observable
?)
It's not necessary for @Observable
, which backs every computed property by a stored property. The dictionary storage example in the proposal is a proxy for use cases that back all properties by some external storage mechanism, such as @Model
from SwiftData or the Realm
use-case mentioned at the end of the SE-0385 motivation:
Consider the
Persisted
property wrapper from the Realm package:@propertyWrapper public struct Persisted<Value: _Persistable> { ... } class Dog: Object { @Persisted var name: String @Persisted var age: Int }
To support advanced schema customization, the property wrapper could store a string that provides a custom name for the underlying database column, specified in the attribute arguments, e.g.
@Persisted(named: "CustomName")
. However, storing this metadata in the property wrapper requires additional storage for each instance of the containing type, even though the metadata value is fixed for the declaration the property wrapper is attached to. In addition to higher memory overload, the metadata values are evaluated eagerly, and for each instantiation of the containing type, rendering property-wrapper instance metadata too expensive for this use case.
[...]
Combined with attached macros, the
@Persisted
property wrapper in Realm can evolve into a macro attached to persistent types, combined with custom metadata attributes that provide schema customization for specific declarations
I believe these use-cases really do want diagnostics in the case where you forget to assign to one of the computed fields in an initializer.

Yeah, I'm thinking of cases where it would be (dynamically) valid to call
didSet
, similar to:
Ah, we totally could make this work using the same strategy in definite initialization that chooses between an init
accessor call or a set
accessor call, but this proposal does not include this change. However, if an assignment to a computed property in an initializer is re-written to a set
accessor call and that set
accessor assigns to a stored property, the observer for that stored property will indeed be called (this is not new behavior in this proposal). But inside of an init
accessor, property observers are never called.

I don't think
init
accessors are any more special thanget
andset
accessors.
You might be right. It really depends from which angle we see it.
My arguments would be:
init
is different from getter and setter because it participates the type initialisation process.- this syntax will leave getter and setter syntax untouched, which mean even we have init method, we can still return value without having to use
get {}
But I agree it's true that this syntax is kind of alien to current Swift syntax. Same as the proposed init
method
Again, I fully support the idea. These are just syntax considerations. Thanks.
i think that this change is needed, altough it makes the language another bit more complicated, a direction that i know is needed by the necessities of us all, but i'm bit scaried of the risk of becoming "a gargantuan language" just like c++ with all this keyword and syntaxes
I just have a curiosity, this change would also enable apple engineers to resolve problems like the "core data requires that the not optional values should have a default value." when using SwiftData+CloudKit on the iOS17 Beta?
Assuming I'm understanding this proposal correctly, this is trying to solve the problem that you can't really do anything in an initializer without falling afoul of definite initialization rules, because the compiler doesn't "peer into" the implementation of what you call. Technically, what I would say is happening is that self
is not fully initialized at the point of the thing you're trying to call, and the proposal is trying to specify a way to hand around a partially constructed object by explicitly specifying which parts of self
it is allowed to touch and which parts it initializes.
The reason I describe this so generally is that the proposal seems to have a motivating example of property wrappers/macros, but I don't actually think property wrappers are the most common gap where something like this is needed. Instead, it looks something like this:
struct Foo {
enum Bar {
case a(A)
case b(B)
}
let bar: Bar
let common: SomethingInCommon
init(a: A) {
bar = .a(a)
commonInit()
}
init(b: B) {
bar = .b(b)
commonInit()
}
func commonInit() {
common = doSomeCommonInitialization()
}
}
This doesn't work today, of course: you can't can't factor out the common parts of initializers because you can't call anything on self
yet, and the other functions aren't allowed to initialize read-only members anyways. This new proposal does actually give a spelling to this pattern, but it reads pretty poorly, probably something like this:
var _commonInit: Void {
init initializes(common) {
common = doSomeCommonInitialization()
}
get { fatalError() }
set { fatalError() }
}
Obviously, this is because we're abusing a feature built for computed properties to do more general initialization. I think what this really shows is that the need here goes beyond just properties. To that end, I would like to see consideration of the concept of "this block of code accesses these fields and initializes some other fields, and is morally kind of operating as if it was part of an initializer" rather than "this property wrapper accesses these fields and initializes some other fields, and is morally kind of operating as if it was part of an initializer". It may be that we decide that we want to start with property wrappers for now and punt the more general version to future directions, but I do think it is important that even if we do that the syntax we pick now has a natural extension to this more general case should we choose to implement it at some point.

"core data requires that the not optional values should have a default value."
Yes. That issues comes from the Observation macro. If you use self.property = …
inside an init from an observable type, that means that you're calling the computed property and not really initializing the stored property. That's why this feature would resolve the problem as the Observable macro would start synthesizing the init
accessors to properly reroute such assignments via the computed properties to their baking storage.
The last development snapshot available for download is from June 7. it would be great to have most recent one with this experimental feature available.
+1 from me.
A memberwise initializer will not be synthesized if a stored property that is an
accesses
effect of a computed property is ordered after that computed property in the source code:
This limitations seems artificial. Compiler should be able to compute topological sorting of the initializations. Or am I missing something? But that's a really minor thing. If it comes with a noticeable performance hit, I'd rather manually reorder property declarations once, than have increased compilation times on every build.

[FURTHER EDIT] Fun test case, if you don't have it already:
@inlinable init
initializesa
(computed), which initializesb
(also computed), which initializesc
(stored).
Do I understand correctly that initializes
and accesses
can list only stored properties?
That is a bit unfortunate. The feature itself attempts to provide abstraction over initializing stored vs computed properties, but in this aspect abstraction is leaking - when initializing property inside init accessor one needs to know if it is a stored or computed property. Does macro API allow to query that and obtain a list of properties in the effects?

However, if an assignment to a computed property in an initializer is re-written to a
set
accessor call and thatset
accessor assigns to a stored property, the observer for that stored property will indeed be called (this is not new behavior in this proposal). But inside of aninit
accessor, property observers are never called.
Great, this is exactly the extension I’d expect of the current “never call observers from assignments that are lexically within an init
” rule.

I haven't yet received a single response from @hborla in the pitch thread nor in any other threads on the evolution forums as I am being passively aggressively ignored in those threads.
I am not ignoring you. I incorporated your capture-list-style syntax suggestion, along with the other suggestions at the end of the pitch thread, into the Alternatives Considered section, and I already justified (twice) in the pitch thread why we cannot simply infer initializes
and accesses
.
When the init
accessor initialize all of self
, is initializes(self)
a valid spelling?

I think what this really shows is that the need here goes beyond just properties. To that end, I would like to see consideration of the concept of "this block of code accesses these fields and initializes some other fields, and is morally kind of operating as if it was part of an initializer" rather than "this property wrapper accesses these fields and initializes some other fields, and is morally kind of operating as if it was part of an initializer". It may be that we decide that we want to start with property wrappers for now and punt the more general version to future directions, but I do think it is important that even if we do that the syntax we pick now has a natural extension to this more general case should we choose to implement it at some point.
Because initializes
and accesses
are modeled as effects here, it seems plausible that a follow-up proposal could allow using those effects on instance methods. Those methods would then have the same semantics as init
accessors - you cannot access all of self
, you can only access the stored properties in the accesses
list, and you must initialize the properties in the initializes
list on all paths. There would be some other restrictions on where you're actually able to call those methods, and which other methods are allowed to be called from inside the implementation, but I don't believe anything in this proposal rules out this direction.
The problem with C++ isn't really the number of features, it's the number of features that both completely replace existing features while either having their design severely compromised for compatibility with prexisting features or being incompatible with those features in unpleasant and subtle ways. Often both.
All this plus WG21's bizarre fetish for making things that ideally would have some language support into library only features (std::optional, std::variant, std::forward & move, etc.).
Swift is a lot better about this because most of the new features (that aren't just sugar) don't really replace old features, tend not to have baffling interactions with existing features, and mostly only need to be known and understood by the person actually using the feature because Swift likes progressive disclosure.
Back on topic: It makes property wrappers less magic, but it's a bit verbose. I don't see a way to make it more concise without making it an unreadable mess, so I'm tentatively for this proposal. +0.5
Would it be feasible to add a didInit
listener to a stored property, the same way one can add willSet
or didSet
without having to make it computed?
Maybe the initialized properties could be an effect similar to mutating
/nonmutating
in setters/getters. These modifiers declare how implicit self is passed. For instance, in a struct property's setter, self
is passed as inout self
by default. Similarly, if we generalize init to the initializing
ownership modifier (similar to borrowing
and consuming
), self._initializedProperty
would be passed as initializing _initializedProperty
.
struct MyStruct {
var _a: Int
var a: Int {
nonmutating get { // Implies: `[borrowing self: Self] in`
_a
}
mutating set { // Implies: `[mutating self: Self] in`
_a = newValue
}
initializes(_a) init { // Implies:
// `[initializing self._a: Int] in`
_a = newValue
}
}
}
As for the awkwardness of the future direction combining the set
and init
implementations, we could just separate the accessors with a comma:
// ...
mutating set, initializes(_a) init {
_a = newValue
}

This limitations seems artificial. Compiler should be able to compute topological sorting of the initializations. Or am I missing something? But that's a really minor thing. If it comes with a noticeable performance hit, I'd rather manually reorder property declarations once, than have increased compilation times on every build.
@Jumhyn has made the same argument. Setting aside whether or not it's feasible, I think what's currently in the proposal leads to the most understandable behavior, because the order of the parameter list should match the order of initialization in the member-wise initializer (which I do not think we should change), which means that a change in dependencies could break all call-sites of your initializer. Topological sorting also just doesn't seem worth it the complexity to me. Do either of you have any use cases that would greatly benefit from this behavior?

Do I understand correctly that
initializes
andaccesses
can list only stored properties?
That's correct. accesses
fundamentally cannot list computed properties, because get
accessors have access to self
(unless we had this generalization of these effects). Perhaps initializes
could be generalized to include computed properties with init
accessors in the future.

Does macro API allow to query that and obtain a list of properties in the effects?
initializes
and accesses
are part of the syntax tree, so a macro can gather all of the listed property names.