swiftgenericsswift-protocolsprotocol-extensionswift3.2

Swift Protocol Extension with AssociatedType Constrained to Collection, Can't Use Subscript


I'm trying to write a protocol that conforms to the Collection Protocol, and it has an associatedType - Object and a property object.

protocol DDCDataSource: Collection
{
    associatedtype Object
    var object: Object {get set}
}

I want to add some default functionality for the case where Object also conforms to the Collection protocol, namely just directly return Object's implementation of these required Collection properties and functions. It seems like it all works except for Collection's requirement for a subscript.

Cannot subscript a value of type 'Self.Object' with an index of type 'Self.Object.Index'

enter image description here

extension DDCDataSource where Object: Collection
{
    typealias Index = Object.Index

    var startIndex: Object.Index {
        get {
            return object.startIndex
        }
    }

    var endIndex: Object.Index {
        get {
            return object.endIndex
        }
    }

    subscript(position: Object.Index) -> Element
    {
        return object[position]
    }

    func index(after i: Object.Index) -> Object.Index {
        return object.index(after: i)
    }
}

Solution

  • Short answer: Change the return type of the subscript method to Object.Element

    subscript(position: Object.Index) -> Object.Element {
        return object[position]
    }
    

    or add a type alias (in a similar way as you did for the Index type)

    typealias Element = Object.Element
    
    subscript(position: Object.Index) -> Element {
        return object[position]
    }
    

    That makes the code compile and run as expected.


    Explanation: The subscript method of Collection is declared as

    subscript(position: Self.Index) -> Self.Element { get }
    

    where Self.Index and Self.Element are associated types of `Collection. With your code

    subscript(position: Object.Index) -> Element {
        return object[position]
    }
    

    the compiler infers Self.Index to be Object.Index, but there is no relation between Self.Element and Object.Element (which is returned by object[position]). The error becomes more apparent if you add an explicit cast:

    subscript(position: Object.Index) -> Element {
        return object[position] as Element
    }
    

    Now the compiler complains

    error: 'Self.Object.Element' is not convertible to 'Self.Element'; did you mean to use 'as!' to force downcast?

    The correct solution is not the forced cast but to make the compiler know that Self.Element is Object.Element, by adding a type alias or by changing the return type

    subscript(position: Object.Index) -> Object.Element {
        return object[position]
    }
    

    so that the compiler infers DDCDataSource.Element to be Object.Element.


    Full self-contained example: (Swift 4, Xcode 9 beta 6)

    (Note that you can omit the get keyword for read-only computed properties.)

    protocol DDCDataSource: Collection {
        associatedtype Object
        var object: Object { get set }
    }
    
    extension DDCDataSource where Object: Collection {
        var startIndex: Object.Index {
            return object.startIndex
        }
    
        var endIndex: Object.Index {
            return object.endIndex
        }
    
        subscript(position: Object.Index) -> Object.Element {
            return object[position]
        }
    
        func index(after i: Object.Index) -> Object.Index {
            return object.index(after: i)
        }
    }
    
    struct MyDataSource: DDCDataSource {
        var object = [1, 2, 3]
    }
    
    let mds = MyDataSource()
    print(mds[1]) // 2
    
    for x in mds { print(x) } // 1 2 3