Search code examples
swiftoperatorsidentity-operator

How do you test the identity of Strings in Swift?


Swift, like a few other languages, has an identity operator ===. But it seems like you can't use it against strings:

var x = "hello"
var y = "hello"
if x === y {         //ERROR: 'String' does not conform to protocol 'AnyObject'
    println("SAME!")
}else {
    println("DIFFERENT!")
}

Is there no straight-forward way of testing the identity of Strings?!... or am I missing something?


Solution

  • Strings in Swift are value types not reference types. That is, they only have value, not identity. In this sense, it does not make sense to check if two strings have the same “identity”. Unless they are the same variable, they are different. Instead, you should just check if they have the same value using ==.

    Under the hood there may be interning. In fact, there definitely is:

    struct StringBits {
        let underlyingPtr: UnsafeMutablePointer<Void>
        let padding1: UnsafeMutablePointer<Void>
        let padding2: UnsafeMutablePointer<Void>
    }
    
    let s1 = "abcd"
    let s2 = "abcd"
    
    let bits1 = unsafeBitCast(s1, StringBits.self)
    let bits2 = unsafeBitCast(s2, StringBits.self)
    
    println(bits1.underlyingPtr) //      0x0000000117654000
    println(bits2.underlyingPtr) // also 0x0000000117654000
    

    Similarly, if you initialize one string from another, they will share the same storage until one of them is mutated (i.e. strings are copy-on-write).

    But these details are hidden from you as implementation details that you aren’t supposed to need to know. As far as the semantics are concerned, s1 and s2 are entirely unrelated.

    If you’re concerned about performance, the underlying String type will probably use the pointers to its storage to efficiently check for equality in cases where they are sharing the same storage. Arrays (which are implemented similarly) do, as can be seen here:

    struct NeverEqual: Equatable { }
    
    // NOT a correct implementation of ==, since == must be
    // reflexive, and this isn’t:
    func ==(lhs: NeverEqual, rhs: NeverEqual)->Bool { return false }
    
    let x=[NeverEqual()]
    let y=x
    
    // this returns true – because it doesn’t bother comparing the 
    // elements, because it knows x and y share the same storage
    x==y