swiftoptionsettype

Why can't we set consecutive rawValue to Option Sets?


Today I tried playing bit with OptionSet in Playground, and I notice this pattern

 struct Activities: OptionSet {
      let rawValue: Int
      static let eating = Activities(rawValue: 1)
      static let programming = Activities(rawValue: 2)
      static let breathing = Activities(rawValue: 3)
      static let saveGotham = Activities(rawValue: 4) 
}

 let act: Activities = [.eating, .programming, .saveGotham]

 act.contains(.breathing). //true /* this is unexpected */
 act.contains(.saveGotham) //true

Although, the array doesn't contain the value '.breathing' it still return true. I modified the same struct with different rawValue

 struct Activities: OptionSet {
      let rawValue: Int
      static let eating = Activities(rawValue: 1)
      static let programming = Activities(rawValue: 8)
      static let breathing = Activities(rawValue: 16)
      static let saveGotham = Activities(rawValue: 32) 
}

 let act: Activities = [.eating, .programming, .saveGotham]

 act.contains(.breathing). //false
 act.contains(.saveGotham) //true

and got the desired output. it would be awesome if someone shed the light on the problem and explain how the 'OptionSet' actually works.

Thank you.


Solution

  • The OptionSet protocol is meant to

    ... represent bitset types, where individual bits represent members of a set.

    In your case,

    let act: Activities = [.eating, .programming, .saveGotham]
    print(act.rawValue) // 7
    

    is stored as an integer containing the BITWISE OR of the raw values (1 | 2 | 4 = 7), and

      act.contains(.breathing). //true /* this is unexpected */
    

    tests if the BITWISE AND 7 & 3 is non-zero (which is the case).

    Therefore you should not use consecutive raw values, but powers of two, i.e. each of the mutually exclusive values is represented by one bit position:

    struct Activities: OptionSet {
        let rawValue: Int
        static let eating = Activities(rawValue: 1)
        static let programming = Activities(rawValue: 2)
        static let breathing = Activities(rawValue: 4)
        static let saveGotham = Activities(rawValue: 8)
    }
    

    or equivalently:

    struct Activities: OptionSet {
        let rawValue: Int
        static let eating = Activities(rawValue: 1 << 0)
        static let programming = Activities(rawValue: 1 << 1)
        static let breathing = Activities(rawValue: 1 << 2)
        static let saveGotham = Activities(rawValue: 1 << 3)
    }
    

    Now everything works as expected:

    let act: Activities = [.eating, .programming, .saveGotham]
    print(act.rawValue) // 11
    print(act.contains(.breathing)) // false
    print(act.contains(.saveGotham)) // true