'FileDocumentWriteConfiguration' cannot be constructed because it has no accessible initializers

While the type discussed herein is part of SwiftUI, I believe this is a general Swift question.

I’m trying to write a unit test for my SwiftUI document class’s fileWrapper(configuration:) method like this:

var doc = … // SwiftUI FileDocument-conforming struct
let fc = FileDocumentWriteConfiguration(contentType: UTType.data, existingFile: nil)
let fileWrapper = try doc.fileWrapper(configuration: fc)

But the compiler (Xcode 15b6) complains that

'FileDocumentWriteConfiguration' cannot be constructed because it has no accessible initializers

Thing is, FileDocumentWriteConfiguration is declared like this in SwiftUI (I’m building for macOS 13.1):

@available(iOS 14.0, macOS 11.0, *)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
public struct FileDocumentWriteConfiguration {
    public let contentType: UTType
    public let existingFile: FileWrapper?
}

It doesn’t get much simpler than this, so why can’t I instantiate it?

Because it has no accessible initializers. Swift never autogenerates a public initializer for a struct, and the mere fact that you can see two public fields doesn't mean that there aren't private or internal ones that also need to be initialized. There's no initializer there in the API you've pasted, so there are no initializers available.

As for why there are no public initializers, that is absolutely a SwiftUI question best asked at the Apple developer forums.

2 Likes

I do this all the time, though:

struct Foo {
    let bar: String
}

let f = Foo(bar: "bar")

So it clearly does autogenerate an initializer. But if it’s like C++, where it won't generate one when there exists another, then perhaps there’s a private one preventing that.

Very confusing, I must say.

Yes, the implicit memberwise initializer is not generated when there are other initializers, for the same reason as C++ doesn't do it. But there may be a different reason here: the implicit memberwise initializer is never public.

As an example of this, try writing:

public struct Foo {
    public let bar: String
}

Then, instead of trying to instantiate it from your module, instantiate it from your tests without importing your module as @testable. You'll find you get the same error.

3 Likes

For a language that lauds its clarity and intuitiveness, it’s really not. Thanks for the clarification.

I disagree. It would be disastrous for Swift to make the implicit memberwise initializer be able to be public.

Swift should never make an API promise for you that you didn't write down. Doing so makes it impossible to maintain libraries, because it becomes altogether too easy to back yourself into a corner.

As an example, imagine that we shipped Foo (above) in v1 of our library, and that Swift did have an implicit public memberwise initializer. That means there is an implicit API function on Foo: init(bar: String).

Then imagine that in v1.1 we want to add a new field to Foo, baz, so we write this:

public struct Foo {
    public let bar: String
    public let baz: Int
}

What is not obvious is that this is API breaking. Because the memberwise initializer is public, you've changed its signature from init(bar: String) to init(bar: String, baz: Int). And you've done it without ever changing a line of code that would clearly indicate the break. That would be disastrous, and make accidental API breakages very likely.

7 Likes

Yeah, that makes perfect sense.