Default initializer is inaccessible

Has the Swift Package Manager encouraged you to create a library for the first time? Are you wondering why the compiler is complaining that an initializer for one of your structs is inaccessible due to internal protection level? This is not a new problem, nor is it specific to Swift Package Manager but if you’re seeing it for the first time it can have you scratching your head.

What’s the problem?

Let’s add some Swift source code file to a Swift package (see creating Swift packages in Xcode for a recap). Here’s an enum and struct I used when playing with filtering with predicates:

// SearchScope.swift
enum SearchByField {
  case name
  case capital
}

struct SearchScope {
  var visitedOnly: Bool = false
  var beginsWith: String = ""
  var searchBy: SearchByField = .name
}

Creating a library forces you to think about the Swift access control model. Each build target, such as your main application, is its own module. In a single target app you don’t have to think too much as the default access level of internal gives you access to your own code unless you choose to restrict things with private or fileprivate.

My Swift library package is its own module. Once I add this source file to the package the internal access level prevents me from using it from the app target. To fix that I need to mark the public facing interface as public:

// SearchScope.swift
public enum SearchByField {
  case name
  case capital
}

public struct SearchScope {
  public var visitedOnly: Bool = false
  public var beginsWith: String = ""
  public var searchBy: SearchByField = .name
}

This looks good but when I build and use the package in my application target I get a compiler error:

var scope = SearchScope() 

SearchScope initializer is inaccessible due to internal protection

What’s going on? I marked everything public and I don’t even have an initializer?

Memberwise Initializers for Swift Structures

It’s easy to forget but if you don’t define your own custom initializers the Swift compiler gives you a default memberwise initializer for free. Even though I’ve now marked the struct as public the default initializer is still internal. From The Swift Programming Language:

if you want a public structure type to be initializable with a memberwise initializer when used in another module, you must provide a public memberwise initializer yourself as part of the type’s definition.

Fixing the problem

A quick Xcode tip to see the default memberwise initializer. Command-click on the struct definition and select “Generate Memberwise Initializer”:

Generate Memberwise Initializer

This adds the default memberwise initializer for you (note that it’s internal):

public struct SearchScope {
  internal init(visitedOnly: Bool = false, beginsWith: String = "", 
                searchBy: SearchByField = .name) {
    self.visitedOnly = visitedOnly
    self.beginsWith = beginsWith
    self.searchBy = searchBy
  }

Change the internal to public and rebuild and you should be good:

// SearchScope.swift
public enum SearchByField {
  case name
  case capital
}

public struct SearchScope {
  public var visitedOnly: Bool
  public var beginsWith: String
  public var searchBy: SearchByField

  public init(visitedOnly: Bool = false, beginsWith: String = "", 
                searchBy: SearchByField = .name) {
    self.visitedOnly = visitedOnly
    self.beginsWith = beginsWith
    self.searchBy = searchBy
  }
}

Read More