Unexpected memberwise inits for all-optional structs

I think I found a bug in how Swift synthesizes memberwise initializers for structs.

Scenario A. Mixed non-optionals and optionals.

struct Foo {
   var a: String
   var b: Int?
}

Synthesizes:

Foo.init(a: String, b: Int?)

Scenario B. All optionals.

struct Foo {
   var a: String?
   var b: Int?
}

Synthesizes:

Foo.init(a: String?, b: Int?)
Foo.init() // why?

Scenario C. All optionals with explicit Optional type.

struct Foo {
   var a: Optional<String>
   var b: Optional<Int>
}

Synthesizes:

Foo.init(a: Optional<String>, b: Optional<Int>)

I have a few questions:

Why would scenario B synthesize a parameterless init, if I haven't explicitly defaulted every property to nil?

And most importantly, why does this behavior only apply to properties declared with optional syntactic sugar ? but not explicit Optional<T>s?

I get the feeling this might be a compiler error.


Side note: how this affects property wrappers

This not only affects struct initializers, but also property wrapper initializers.

Scenario B applied to property wrappers.

@propertyWrapper
struct Bar {
    var wrappedValue: String?
}

struct Foo {
    @Bar
    var a: String?
}

Synthesizes:

Foo.init(a: Bar)
Foo.init()
Foo.init() // repeated... why?

Scenario C half-applied to property wrappers.

@propertyWrapper
struct Bar {
    var wrappedValue: Optional<String>
}

struct Foo {
    @Bar
    var a: String?
}

Synthesizes:

Foo.init(a: String?)
Foo.init()

Scenario C fully-applied to property wrappers.

@propertyWrapper
struct Bar {
    var wrappedValue: Optional<String>
}

struct Foo {
    @Bar
    var a: Optional<String>
}

Synthesizes:

Foo.init(a: Optional<String>)

Conclusion

I expect ? to produce the initializers that explicit Optionals produce. However this is not happening at the moment. Am I correct in my expectation?

1 Like

It’s intentional, ? and Optional<T> have different default initialization rules (the former can be default initialized to nil, but the latter can’t). Personally, I think it’s confusing, but it may be source breaking to change it now.

3 Likes

It is indeed confusing, but the fact that it's source breaking shouldn't stop us from addressing it. That's what major version bumps are for. There's a chance for Swift 6 to correct this.

I understand that in the context of the program's execution, var a: String? will be initialized as nil implicitly, but defining properties in a struct is very different from declaring variables, and IMO it requires a higher level of explicitness about defaults to avoid this kind of inconsistent behavior, especially when it trickles down to synthesized code.

The fact that the syntax is the same doesn't mean it should behave the same. It's all about context.

Yeah, I guess. If I remember correctly, @Slava_Pestov was interested in simplifying the rules but many community members were relying on the current behaviour so it didn’t happen.

Changing the String? case to not default to nil would be source breaking. Changing Optional<String> to default to nil would not be. I’m in favor of that change but that’s not the change suggested before.

The latter option might still be source-breaking as it pertains to generated memberwise initializers, as it could cause ambiguity among overloads where currently there is none. The bigger issue is that it would certainly be contrary to the general direction of Swift, which is to eschew implicit defaults.

The former option was sufficiently source-breaking that the removal of the implicit default was not acceptable to the community back in the Swift 3/4/5 days, so it is hard to see how it would be acceptable now when the source compatibility expectations are even higher.

3 Likes