Swift enum with custom initializer loses rawValue initializer

SwiftEnums

Swift Problem Overview


I have tried to boil this issue down to its simplest form with the following.

Setup

Xcode Version 6.1.1 (6A2008a)

An enum defined in MyEnum.swift:

internal enum MyEnum: Int {
    case Zero = 0, One, Two
}

extension MyEnum {
    init?(string: String) {
        switch string.lowercaseString {
        case "zero": self = .Zero
        case "one": self = .One
        case "two": self = .Two
        default: return nil
        }
    }
}

and code that initializes the enum in another file, MyClass.swift:

internal class MyClass {
    let foo = MyEnum(rawValue: 0)  // Error
    let fooStr = MyEnum(string: "zero")
    
    func testFunc() {
        let bar = MyEnum(rawValue: 1)  // Error
        let barStr = MyEnum(string: "one")
    }
}

Error

Xcode gives me the following error when attempting to initialize MyEnum with its raw-value initializer:

Cannot convert the expression's type '(rawValue: IntegerLiteralConvertible)' to type 'MyEnum?'

Notes

  1. Per the Swift Language Guide: > If you define an enumeration with a raw-value type, the enumeration automatically receives an initializer that takes a value of the raw value’s type (as a parameter called rawValue) and returns either an enumeration member or nil.

  2. The custom initializer for MyEnum was defined in an extension to test whether the enum's raw-value initializer was being removed because of the following case from the Language Guide. However, it achieves the same error result. > Note that if you define a custom initializer for a value type, you will no longer have access to the default initializer (or the memberwise initializer, if it is a structure) for that type. [...]
    > If you want your custom value type to be initializable with the default initializer and memberwise initializer, and also with your own custom initializers, write your custom initializers in an extension rather than as part of the value type’s original implementation.

  3. Moving the enum definition to MyClass.swift resolves the error for bar but not for foo.

  4. Removing the custom initializer resolves both errors.

  5. One workaround is to include the following function in the enum definition and use it in place of the provided raw-value initializer. So it seems as if adding a custom initializer has a similar effect to marking the raw-value initializer private.

     init?(raw: Int) {
         self.init(rawValue: raw)
     }
    
  6. Explicitly declaring protocol conformance to RawRepresentable in MyClass.swift resolves the inline error for bar, but results in a linker error about duplicate symbols (because raw-value type enums implicitly conform to RawRepresentable).

     extension MyEnum: RawRepresentable {}
    

Can anyone provide a little more insight into what's going on here? Why isn't the raw-value initializer accessible?

Swift Solutions


Solution 1 - Swift

This bug is solved in Xcode 7 and Swift 2

Solution 2 - Swift

extension TemplateSlotType {
    init?(rawString: String) {
        // Check if string contains 'carrousel'
        if rawString.rangeOfString("carrousel") != nil {
            self.init(rawValue:"carrousel")
        } else {
            self.init(rawValue:rawString)
        }
    }
}

In your case this would result in the following extension:

extension MyEnum {
    init?(string: String) {
        switch string.lowercaseString {
        case "zero": 
	        self.init(rawValue:0)
        case "one": 
	        self.init(rawValue:1)
        case "two":
	        self.init(rawValue:2)
        default: 
	        return nil
        }
    }
}

Solution 3 - Swift

You can even make the code simpler and useful without switch cases, this way you don't need to add more cases when you add a new type.

enum VehicleType: Int, CustomStringConvertible {
    case car = 4
    case moped = 2
    case truck = 16
    case unknown = -1

    // MARK: - Helpers

    public var description: String {
        switch self {
        case .car: return "Car"
        case .truck: return "Truck"
        case .moped: return "Moped"
        case .unknown: return "unknown"
        }
    }

    static let all: [VehicleType] = [car, moped, truck]

    init?(rawDescription: String) {
        guard let type = VehicleType.all.first(where: { description == rawDescription })
            else { return nil }
        self = type
    }
}

Solution 4 - Swift

Yeah this is an annoying issue. I'm currently working around it using a global-scope function that acts as a factory, i.e.

func enumFromString(string:String) -> MyEnum? {
    switch string {
    case "One" : MyEnum(rawValue:1)
    case "Two" : MyEnum(rawValue:2)
    case "Three" : MyEnum(rawValue:3)
    default : return nil
    }
}

Solution 5 - Swift

This works for Swift 4 on Xcode 9.2 together with my EnumSequence:

enum Word: Int, EnumSequenceElement, CustomStringConvertible {
    case apple, cat, fun
    
    var description: String {
        switch self {
        case .apple:
            return "Apple"
        case .cat:
            return "Cat"
        case .fun:
            return "Fun"
        }
    }
}

let Words: [String: Word] = [
    "A": .apple,
    "C": .cat,
    "F": .fun
]

extension Word {
    var letter: String? {
        return Words.first(where: { (_, word) -> Bool in
            word == self
        })?.key
    }
    
    init?(_ letter: String) {
        if let word = Words[letter] {
            self = word
        } else {
            return nil
        }
    }
}

for word in EnumSequence<Word>() {
    if let letter = word.letter, let lhs = Word(letter), let rhs = Word(letter), lhs == rhs {
        print("\(letter) for \(word)")
    }
}

Output

A for Apple
C for Cat
F for Fun

Solution 6 - Swift

Add this to your code:

extension MyEnum {
    init?(rawValue: Int) {
        switch rawValue {
        case 0: self = .Zero
        case 1: self = .One
        case 2: self = .Two
        default: return nil
        }
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionnickgraefView Question on Stackoverflow
Solution 1 - SwiftalcamlaView Answer on Stackoverflow
Solution 2 - SwiftAntoineView Answer on Stackoverflow
Solution 3 - SwiftcarbonrView Answer on Stackoverflow
Solution 4 - SwiftAshView Answer on Stackoverflow
Solution 5 - SwiftmclamView Answer on Stackoverflow
Solution 6 - SwiftTony SwiftguyView Answer on Stackoverflow