Equality in Set in Swift

I have a type called FeildType which is Equatable , Hashable, Identifiable.
I use a type called UniqueID as my identifier which is defined as below:

public struct UniqueID: Hashable , Equatable , Sendable {
    
    public let fullName : String
    
    public let parentName : String
    
    public var id : String { parentName + "." + fullName }
    
  public static func == (lhs: UniqueID, rhs: UniqueID) -> Bool {
        return rhs.id == lhs.id
    }
    public func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
    
}

and FeildType implemention is like this:

public final class FeildType :  Identifiable , Hashable , Equatable {
   

    /// id used for Hashable , Equatable
    public var id : UniqueID { .init(fullName: fullName, parentName: parentType?.fullName ?? "" ) }
    //....
}
public extension FeildType{
    static func == (lhs: FeildType , rhs: FeildType) -> Bool {
        lhs.id == rhs.id
    }
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

Now I compare 2 Set<FeildType> s in my test case swift sometimes returns false even-though 2 sets have elements with identical ids:

 #expect(optionalInfo.fields.map(\.id).ascending() 
           == person.fields.map(\.id).ascending() ) //Always True

// Note: person.fields and optionalInfo.fields are not arrays, they are Set<FeildType>
 #expect(optionalInfo.fields == person.fields) // Most of the times False

the questions is why? and more importantly why sometimes it fails and sometimes it doesn't?

Your FeildType is a class. Can its id mutate during its lifetime? If so, you're breaking the contract with Set. You shouldn’t change the hash value of an element in a set (or a Dictionary) while it is in the set.

3 Likes

Yes , id can mutate and in fact that's intentional. So I will just dont use Set then.
Thanks :folded_hands:

As a cautionary note: systems are much simpler when IDs are immutable.

One time, I was building a CRUD web app for managing student enrolments. The institution's student IDs seems like a natural choice for a primary key, but that actually turned out to be a total pain. If someone maybe a typo in the student ID field, they couldn't edit it without deleting that record and creating a "new" one under the correct ID.

Adding a separate UUID for internal use ended up greatly simplifying the whole rest of the system.

5 Likes

I am trying to make an open source library to operate on top of SwiftSyntax in order to extract information from swift source code. I also want to add some limited type inference in it. :smiling_face:

Since in Swift you don't need to code in-order ( unlike C for example that you have to define your type before creating variable of it), I need id of my objects to update over time ( as I gather more information later on) and that's the main reason I am using class instead of struct cause sharing data is actually intended here.

While I totaly agree with you , in fact I was using UUID at first , but it makes the problem much harder to deal with. I ended up with creating my own UniqueArray array type :grinning_face: