AJ Venturella
AJ Venturella

Reputation: 4912

Swift Casting Generic to Optional with a nil value causes fatalError

Using Swift 2, in my contrived example I am converting a String to an Int or more specifically an Int or an Int? using a generic. In the case where the Int? should be nil the cast will fail with a fatalError: fatal error: unexpectedly found nil while unwrapping an Optional value

These look like they may be similar/duplicate questions:

My question is: how is one supposed to cast to an optional that is nil?

Example:

class Thing<T>{
    var item: T

    init(_ item: T){
        self.item = item
    }
}

struct Actions{

    static func convertIntForThing<T>(string: String, thing:Thing<T>) -> T{
        return convertStringToInt(string, to: T.self)
    }

    static func convertStringToInt<T>(string: String, to: T.Type) -> T{
        debugPrint("Converting to ---> \(to)")

        if T.self == Int.self{
            return Int(string)! as! T
        }

        // in the case of "" Int? will be nil, the cast
        // here causes it to blow up with:
        //
        // fatal error: unexpectedly found nil while unwrapping an
        // Optional value even though T in this case is an Optional<Int>
        return Int(string) as! T

    }
}


func testExample() {
    // this WORKS:
    let thing1 = Thing<Int>(0)
    thing1.item = Actions.convertIntForThing("1", thing: thing1)

    // This FAILS:
    // empty string  where value = Int("")
    // will return an Optional<Int> that will be nil
    let thing2 = Thing<Int?>(0)
    thing2.item = Actions.convertIntForThing("", thing: thing2)
}

testExample()

Upvotes: 2

Views: 2366

Answers (3)

Qbyte
Qbyte

Reputation: 13243

Generally casting in Swift has sometimes weird behaviors when you are working with generic types. A simple workaround would be to make a generic casting function:

func cast<T, U>(value: T, type: U.Type) -> U {
    return value as! U
}

Now you can rewrite the casting to:

return cast(Int(string), type: T.self)

Upvotes: 0

AJ Venturella
AJ Venturella

Reputation: 4912

I got something that does work.

I forgot that Optional is a NilLiteralConvertible. So we can provide 2 variations on the conversion function and it will not fail. Basically, provides a constraint on T where T: NilLiteralConvertible

class Thing<T>{
    var item: T

    init(_ item: T){
        self.item = item
    }
}

struct Actions{

    // Provide 2 variations one with T the other where T: NilLiteralConvertible
    // variation 1 for non-optionals
    static func convertIntForThing<T>(string: String, thing:Thing<T>) -> T{
        return convertStringToInt(string, to: T.self)
    }

    // variation 2 for optionals
    static func convertIntForThing<T where T: NilLiteralConvertible>(string: String, thing:Thing<T>) -> T{
        return convertStringToInt(string, to: T.self)
    }

    static func convertStringToInt<T>(string: String, to: T.Type) -> T{
        debugPrint("Converting to ---> \(to)")
        return Int(string)! as! T
    }

    static func convertStringToInt<T where T: NilLiteralConvertible>(string: String, to: T.Type) -> T{
        debugPrint("Converting to ---> \(to)")

        let value = Int(string)

        if let _ = value{
            return value as! T
        }

        let other: T = nil
        return other
    }
}

func testExample() {
    // this WORKS:
    let thing1 = Thing<Int>(0)
    thing1.item = Actions.convertIntForThing("1", thing: thing1)

    let thing2 = Thing<Int?>(0)
    thing2.item = Actions.convertIntForThing("", thing: thing2)
}

testExample()

Upvotes: 2

matt
matt

Reputation: 535306

You can't cast nil to some-kind-of-nil, but you can make some-kind-of-nil, as this artificial example shows:

    func test(s:String) -> Int? {
        var which : Bool { return Int(s) != nil }
        if which {
            return (Int(s)! as Int?)
        } else {
            return (Optional<Int>.None)
        }
    }

    print(test("12"))
    print(test("howdy"))

Upvotes: 2

Related Questions