Extending a non-final class to confirm to a protocol requiring a static function returning Self

OK… What am I getting wrong here? I’ve created a gist if you would like to demonstrate how much cleverer than me you are! All thoughts and ideas are welcome… Pop this in a playground… you aren’t allowed to make the caller cast… or declare the class final (as I don’t want to put that requirement on my consumers)…

import Foundation

public protocol BigData {
    static func cache(from path:String)->Self
}

extension String : BigData {
    public static func cache(from path: String) -> String {
        return (try? String(contentsOfFile: path)) ?? "Could not load"
    }
}

class MyDatabase {

}

extension MyDatabase : BigData {
  // static func cache(from path: String) -> BigData doesn't work, 'MyDatabase' doesn't conform to protocol "Big. Data"
    static func cache(from path: String) -> Self {
        // return MyDatabase() as Self doesn't work: 'Self' is only available in a protocol or as the result of a method in a class; did you mean 'MyDatabase'?
        return MyDatabase()         // Cannot convert return expression of type 'MyDatabase' to return type 'Self'
    }
}

// I don't want the consumer of this API to have to cast...
let stringCache     : String     = String.cache(from: "/SomeFile.db")
let databaseCache   : MyDatabase = MyDatabase.cache(from: "/SomeOtherFile.db")

Thoughts?

The reason you can’t cast to Self is clear from the error, I assume. Even if you could cast, it would always fail unless Self is MyDatabase (casting to a subtype).

You have to grab on to something that returns Self as well.
For instance,

static func cache(from path: String) -> Self {
   return self.init()
}

I think I see it now. MyDatabase may be sub-classed later, but the static function in the extension would go to that sub-class. So we can’t return the conforming MyDatabase, it must remain self. Returning Self won’t work for the same reasons, Self may not continue to be MyDatabase.

Not sure this would fit your needs:

Like you have mentioned in your original post, you could return BigData

And add common methods for all databases in it. So the consumer of your API need not know the underlying instance of Database (String / MyDatabase) being used. As long as it is a type that conforms to BigData it would be fine.

public protocol BigData {
    
    static func cache(from path:String)->BigData
    
    //You could even have some additional common functions in here, so that you don't need to know the specific instance of database.
    func save()
    func clear()
}

extension String : BigData {
    public static func cache(from path: String) -> BigData {
        return (try? String(contentsOfFile: path)) ?? "Could not load"
    }
    
    public func save() {}
    public func clear() {}
}

class MyDatabase {}

extension MyDatabase : BigData {

    static func cache(from path: String) -> BigData {
        return MyDatabase()
    }
    
    public func save() {}
    public func clear() {}
}

let cache1   : BigData     = String.cache(from: "/SomeFile.db")
let cache2   : BigData     = MyDatabase.cache(from: "/SomeOtherFile.db")

cache1.save()
cache2.save()

That's an interesting idea. Thank you