31
votes

How do I determine whether two instances of a generic struct are of the same type?

For example, given the following struct:

struct FooBar<T> {
    let variable: T
    init(arg: T) {
        variable = arg
    }
}

And the following snippet:

let foo = FooBar(1)
let bar = FooBar(1.0)
let baz = FooBar("1")

How can I determine whether foo, bar, or baz are of the same or different types?


func areExactType(x: FooBar) -> Bool {
    return self.dynamicType === x.dynamicType
}

This gives

Type 'Foo' does not conform to protocol 'AnyObject'


func areExactType(x: FooBar) -> Bool {
    return self.dynamicType === x.dynamicType
}

This gives

Cannot invoke '==' with an argument list of type '(Foo.Type, Foo.Type)'


func areExactType(x: FooBar) -> Bool {
    return self is x.dynamicType
}

This gives three errors:

Consecutive statements on a line must be separated by ';'

(this wants to put a semicolon between the period and 'dynamicType')

Expected identifier in dotted type

and

Expected expression

3
I just found this question after google searching. Future me thanking past me.nhgrif

3 Answers

13
votes

Edit:

Sorry for the premature answer, it actually doesn't work, because the compiler will choose the function for different types when called from within another function:

func foobar<T,U> (lhs: Foo<T>, rhs: Foo<U>) -> Bool {
    return lhs.sameType(rhs)
}

If you stay in pure Swift territory, the following will work:

Given a simple generic struct

struct Foo<T> {
    let v : T
}

You can define a function sameType that takes to Foos of the same type and just return true:

func sameType<T> (a: Foo<T>, b: Foo<T>) -> Bool {
    return true
}

and overload the function with two different Foos:

func sameType<T,U> (a: Foo<T>, b: Foo<U>) -> Bool {
    return false;
}

The compiler will choose a method based on the argument type:

let a = Foo(v: 1.0)
let b = Foo(v: "asdf")
sameType(a, b) // false
sameType(a, a) // true

This works the same way with instance methods on the struct:

    func sameType (other: Foo) -> Bool {
        return true
    }

    func sameType<U> (other: Foo<U>) -> Bool {
        return false
    }

This can have unexpected results if you mix Swift and Objective-C or have to rely on the dynamic type for other reasons:

import Foundation
let x = NSArray(object: 1)
let y = NSArray(object: "string")
sameType(Foo(v: x[0]), Foo(v: y[0])) // true

The result is true because because Foo(v: x[0]) has type Foo<AnyObject>

7
votes

Taking some inspiration from Sebastian's answer, I came up with this solution:

func sameType<L,R>(left: L, right: R) -> Bool {
    if let cast = left as? R {
        return true
    } else {
        return false
    }
}

This works even if nested within a function accepting generics:

func compare<T,U>(foo: T, bar: U) -> Bool {
    return sameType(foo, bar)
}

But it does have some of the downfalls that Sebastian's answer mentioned. Namely, if you've retrieved your values from an Objective-C collection, they'll both have a type of AnyObject. Moreover, if we nest my sameType function within a function whose arguments are not generics, such as:

func compare(foo: Any, bar: Any) -> Bool {
    return sameType(foo, bar)
}

The sameType function here will always return true. The type of foo and bar, as far as sameType is concerned, is Any, rather than whatever most specific type I could potentially downcast it to.


It's also probably worth noting that if I try nesting this as an instance method, Xcode crashes. Read that again, Xcode crashes. Not "produces undesired results". The application crashes.

For example:

func sameType<L,R>(left: L, right: R) -> Bool {
    if let cast = left as? R {
        return true
    } else {
        return false
    }
}

struct Foo<T> {
    func sameType<U>(bar: U) -> Bool {
        return sameType(self, bar)
    }
}

let x = Foo<Int>()
let y = Foo<Float>()

if x.sameType(y) {
    println("x is a y")
}

This code snippet crashes Xcode. I have no idea why... but it does...

If we instead write:

if sameType(x,y) {
    println("x is a y")
}

Xcode compiles and runs just fine.

2
votes

If I haven't misunderstood, you don't want to know if variables of type FooBar are of the same type (because they are), you want to check whether they are using the same generic type.

Since the struct already contains a property of the generic type, you can use it for type comparison, rather than using the struct itself:

func areExactType<U>(x: FooBar<U>) -> Bool {
    return x.variable is T
}

I've tested in a playground, and it works with basic data types, arrays, dictionaries etc. The downside is that in order for it to work, the struct must have a property of the generic type.