Swift: Raw{Not}Representable enum

Preconditions: Xcode Version 6.3 (6D532l), Swift 1.2

In Swift

An enumeration defines a common type for a group of related values and enables you to work with those values in a type-safe way within yout code

|

Enumerations are first-class types in their own right. Use enum to create an enumeration.

Trivial and easy example of enumeration

enum MyEnym: Int {
    case One, Two
}

In this simple example, the raw value of enumeration is type of Int. Now here is the thing: create enumeration where value is of custom type.

Raw representable enumeration

Enumerations in Swift can be represented by raw value of any type. This is very powerful tool to map values with the different representations, one of the application of these abilities is interchability between Integer and Enum value, for example let's say One is represented by integer equals 1, while Many is prepresented by any other value.

I found that protocol RawRepresentable is the one to do the job. Thanks to this characteristic I can easily manipulate what the values are under the hood.

RawRepresentable - A type that can be converted to an associated "raw" type, then converted back to produce an instance equivalent to the original.

Here are "One, Many enum values" represantable as integers with values 1 for "One" and 9223372036854775807 for "Many" (just to make it not obvious here)

enum MyEnum: RawRepresentable {
    case One, Many

    typealias RawValue = Int
    var rawValue: RawValue {
        return self == .One ? 1 : Int.max
    }
    init?(rawValue: RawValue) {
        self = rawValue == 1 ? .One : .Many
    }
}

and usage:

but, wait.... check out this mysterious type: RawValue. For some reason this is a known type for compiler, but in fact this type is not defined for this scope. This is internal type for MyEnum. Why this is possible at all? because of that definition:

var rawValue: RawValue

I'd expect that RawValue become Int outside the enum scope, or not allowed to use explicite as return value (the first approach makes more sense to me). RawValue type is simly not usable out of as raw value for any other RawRepresentable enum.

I should write this:

var rawValue: Int

since here "Int" is the type aliased as "RawValue".

enumeration with custom type

Enum requested at the begining of this post (enumeration where value is of custom type) can be constructed like this:

struct MyType {
    var count:Int
}

enum MyEnum: RawRepresentable {
    case One, Many

    typealias RawValue = MyType
    var rawValue: RawValue {
        return self == .One ? MyType(count: 1) : Int.max
    }
    init?(rawValue: RawValue) {
        self = rawValue.count == 1 ? .One : .Many
    }
}

where MyType is my custom type, which is setup as RawValue for a enum

let myenum = MyEnum(rawValue: MyType(count: 1))

that's it.

initialize enum value

For enums of type Int, I don't have to manually implement RawRepresentable protocol, and I can initialize the value along the definition (sugar syntax). Swift have this convenience syntax to initialize enum value with raw value:

case One = 1, Many = 9223372036854775807

however it can't be used with just RawRepresentable enum because of error: enum case cannot have a raw value if the enum does not have a raw type

hm... I don't agree on that since my enum conforms to RawRepresentable and definitely have raw type:

enum MyEnum: RawRepresentable {
    typealias RawValue = Int
    case One = 1, Many = 9223372036854775807 // error
    (...)
}

So how can I use it? Enum type must be of given type, Int in this case:

enum MyEnum: Int {
    case One = 1, Many = 9223372036854775807
    (...)
}

Now... let's assume I don't want Int for some imaginary reason here, so I should create my own struct type

struct MyType {}

here is my new declaration of MyEnum

enum MyEnum: MyType {
  (...)
}

then error: raw type 'MyType' is not convertible from any literal

what type is literal convertible?

ok, any literal, how about NilLiteralConvertible?

struct MyType: NilLiteralConvertible {
    init(nilLiteral: ()) {
    }
}

NOPE. raw type 'MyType' is not convertible from any literal. I would argue with that, but ok.... let's do some research. Try ArrayLiteralConvertible:

struct MyType: ArrayLiteralConvertible {
    typealias Element = [Int]
    init(arrayLiteral elements: Element...) {
    }
}

nope, this is not literal convertible enough, neither: BooleanLiteralConvertible, DictionaryLiteralConvertible

after a while of tests, I came to the conclusion that these are true literal convertible protocols for enum:

  • FloatLiteralConvertible
  • IntegerLiteralConvertible
  • ExtendedGraphemeClusterLiteralConvertible
  • StringLiteralConvertible
  • UnicodeScalarLiteralConvertible

and these are not

  • ArrayLiteralConvertible
  • DictionaryLiteralConvertible
  • NilLiteralConvertible*
  • BooleanLiteralConvertible*

* surprise, surprise.

When compiler said (computer is talking to me, oh god) "raw type 'MyType' is not convertible from any literal" he didn't tell what exactly it is about, because as I shown above, it is not about literal convertibility as I know it. I might never know what it is about ;) Just please don't be some random method from String or Int hierarchy. Actually my guess is this is hardcoded, but it's just my guess

Conclusion

I still use enums and I love it. I believe issues described here will be addressed at some point, since I think these are compiler limitations, not flaws of design.

Catch me at Twitter @krzyzanowskim

Cover photo: https://www.flickr.com/photos/tahini/4047887309