Search code examples
swifttypescastingprotocolsextension-methods

swift static protocol functions called generically from array


I have a project in which i want to document by structs in a certain way. Each struct should have a static method document which returns a String. I wanted to write an extension for Array so that i can call the function on an array of objects conforming to my protocol, but it's not working..

So far I got this:

protocol Documentable {
  static func document() -> String
}

extension Array: Documentable where Element == Documentable {
  static func document() -> String {
    guard let casted = Element.Type.self as? Documentable.Type else {
      return "didn't work..."
    }

    return "[\(casted.document()), ...]"
  }
}

struct StructA: Documentable {
  static func document() -> String {
    return "a"
  }
}

// approach 1
// this leads to "didn't work..."
let a = Array<StructA>()
print(type(of: a).document())

// approach 2
// referencing static method 'document()' on 'Array' requires the types 'StructA' and 'any Documentable' be equivalent
let b = Array<StructA>.self
print(b.document())

Approach 1 compiles, but leads to a runtime error because the casting somehow doesn't work, which I feel like makes little sense, since Element has to conform to my protocol, so the cast should always work..

Approach 2 doesn't compile with the message:

referencing static method 'document()' on 'Array' requires the types 'StructA' and 'any Documentable' be equivalent

which is also weird because StructA does conform to my protocol..

it may have something to do with this but i'm not sure how to do this then..


Solution

  • Your code adds an extension on the single type of Array<Document>, not Array<T> for all T that conforms to Document.

    Element.document() obviously cannot exist for Array<Document>. Since Element is the same as Document, Element.document() has no implementation. See also the post you linked.

    You should change == to :. That's probably what you meant to do.

    extension Array: Documentable where Element: Documentable {
        static func document() -> String {
            "[\(Element.document()), ...]"
        }
    }
    

    And now you can do Array<StructA>.document(), and that will return "[a, ...]".