How can I have a default init(fromPath:) for all the structs that conform to a protocol?

I tried the following but it errors out (check inline comments). How can I get this working?

protocol Loader {
    var contents: String {get set}
    init(fromPath: String)  // this must be shared
    func transform()  // each will have its own implementation
}

extension Loader {
    init(fromPath: String) {
        // error: 'self' used before 'self.init' call or assignment to 'self'
        // error: 'self.init' isn't called on all paths before returning from initializer
        contents = "read file from fromPath and assign contents here"
    }
}

struct DocFileLoader: Loader {
    var contents: String 
    func transform() {}
}

struct CSVFileLoader: Loader {
    var contents: String 
    func transform() {}
}

PS: I checked Init in protocol extension but I could not follow the discussion as I feel (I maybe wrong here) it evolved later into a different problem.

Only if the default implementation delegates to another initialiser defined on the protocol or one of its ancestors.

protocol Foo {
  init(_: Int)
  init(_: Double)
}
extension Foo {
  // Works.
  init(_ x: Int) {
    self.init(Double(x))
  }
}

But you can't have a default implementation which provides a memberwise initialiser (one which initialises all the members itself, as in your example), because the object may have more members than the protocol knows about:

protocol Loader {
    var contents: String {get set}
    init(fromPath: String)
}

struct OhNo: Loader {
  var contents: String
  var whoInitialisesThis: SomeType
}
3 Likes

Looks like a default constructor is needed here since while parsing the extension the compiler has no chance of knowing what the actual object will look like or for example how contents will be redefined there.

protocol Loader {
	var contents: String { get set }
	init()
	init(fromPath: String)  // this must be shared
	func transform()  // each will have its own implementation
}

extension Loader {
	init(fromPath: String) {
		self.init()
		contents = "read file from fromPath and assign contents here"
	}
}
1 Like

I'd change the way you structure types to avoid the need to have default init in protocol:

protocol ContentTransformer {
    func transform(contents: inout String)
}

struct Loader<Transformer> where Transformer: ContentTransformer {
    var contents: String
    let transformer: Transformer

    init(fromPath path: String, transformer: Transformer) {
        self.transformer = transformer
        contents = // read from path
    }

    func transform() {
        transformer.transform(contents: &contents)
    }
}
2 Likes