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

949 views Asked by At

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)
    }
}
3

There are 3 answers

0
Martin R On BEST ANSWER

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
2
dengApro On

Firstly, I think you should define Element,

Secondly, you use object[position], Object Conforms To Collection , but it is not of Collection Types . Obviously it is not Array.

As apple says: array conforms to CustomDebugStringConvertible / CustomReflectable / CustomStringConvertible / CVarArg /Decodable / Encodable / ExpressibleByArrayLiteral /MutableCollection /RandomAccessCollection / RangeReplaceableCollection

I think extension DDCDataSource where Object: Array is better.

And the element in array shall be Element defined. Just tips.

2
Torongo On

Try this:

subscript(position:Object.Index) -> Element
    {
        var element: Element
        guard let elementObject = object[position] else {
            //handle the case of 'object' being 'nil' and exit the current scope
        }
        element = elementObject as! Element

    }