Search code examples
arraysgenericsswiftuint

Swift Cast as Generic crashing with UInt


Why does the following Array extension crash if the array used with it is of type UInt, but works if the array is of type Int or String?

extension Array
{
func indexOf<T:Equatable>(value:T) -> Int?
{
    for (i, val) in enumerate(self)
    {
        if (val as T == value)
        {
            return i;
        }
    }
    return nil;
}
}

var a:[UInt] = [243, 234, 1, 212, 3, 56, 88, 11, 77];
var i = a.indexOf(234);

Error produced:

Playground execution failed: error: Execution was interrupted, reason: EXC_BREAKPOINT (code=EXC_I386_BPT, subcode=0x0). The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation. * thread #1: tid = 0x27a3c, 0x00000001079d3f27 libswift_stdlib_core.dylibswift_dynamicCast + 1063, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=EXC_I386_BPT, subcode=0x0) * frame #0: 0x00000001079d3f27 libswift_stdlib_core.dylibswift_dynamicCast + 1063 frame #1: 0x00000001137bbbc8


Solution

  • The problem is that the T: Equatable that you define is unrelated to the T that is stored in the array. When you do val as T, you are converting from the array value type to your new local T that is required to be equatable.

    When you call indexOf with a literal, it is not being forced into the same type as is stored in a because the type is not enforced to match by your extension.

    You are better off with a function to get the index of an object in an array:

    func indexOfObject<T : Equatable>(object: T, inArray collection: [T]) -> Int? {
        var index : Int = 0
        for testObject in collection {
            if testObject == object {
                return index
            }
            index++
        }
        return nil
    }
    

    That strictly enforces that the T type is equatable and matches the type of the object passed in.

    Better yet, you might want to use an extension like this:

    extension Array {
        func indexOfObjectPassingTest(test: (object: T) -> Bool) -> Int? {
            var index : Int = 0
            for object in self {
                if test(object: object) {
                    return index
                }
                index++
            }
            return nil
        }
    }
    

    That is more flexible and allows you to do:

    var i = a.indexOfObjectPassingTest({$0 == 234})
    

    Notice also that I do not define T for my method because it is already defined in the Array itself.