2

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()
Community
  • 1
  • 1
AJ Venturella
  • 4,742
  • 4
  • 33
  • 62
  • "how is one supposed to cast to an optional that is nil" You can't. The question makes no sense. There is no there there - there's nothing to cast. It is nil. You can _test_ it but you can't _cast_ it. – matt Aug 26 '15 at 17:25
  • It can be changed to return `nil` but then the compiler complains that nil is incompatible with return type `T`. In the case where `T` is `Int?` though, `nil` would be a valid value. – AJ Venturella Aug 26 '15 at 17:30
  • You have to _make_ the correct kind of nil. Try returning `Optional.None`. – matt Aug 26 '15 at 17:36
  • But I grant that this may not work for a generic. I've seen lots of issues where the generic placeholder type is supposed to be an Optional. It just doesn't work. – matt Aug 26 '15 at 17:39
  • ya, even if I do this: `return Optional.None as! T` it blows up... I'm thinking your last comment is probably spot on. Optional Generics are problematic. Same fatalError, `fatal error: unexpectedly found nil while unwrapping an Optional value` – AJ Venturella Aug 26 '15 at 17:43
  • Yeah, I think if you do a search you will find a _lot_ of questions about generics where the placeholder is supposed to be resolved to an Optional. The problem is that there is not _inherent_ relationship between an Optional and the thing it wraps; the Optional is a _different type_, a type that is _itself_ a generic. – matt Aug 26 '15 at 17:52
  • I got something that does work. I forgot that Optional is a `NilLiteralConvertible`. So when I do this https://gist.github.com/aventurella/dd67b6394c87d5551e74 it doesn't fail. Basically provides a constraint on `T where T: NilLiteralConvertible` – AJ Venturella Aug 26 '15 at 20:54
  • Cool-o-rama! Answer your own question so I can up vote your answer! – matt Aug 26 '15 at 21:05

3 Answers3

2

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"))
matt
  • 515,959
  • 87
  • 875
  • 1,141
2

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()
AJ Venturella
  • 4,742
  • 4
  • 33
  • 62
  • In 48 hours you can accept your own answer, and you should do so. This is totally normal and correct Stack Overflow behavior, and helps others. – matt Aug 26 '15 at 22:31
0

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)
Qbyte
  • 12,753
  • 4
  • 41
  • 57
  • That also worked. In the case of the `Int?` I had to do this with that casting function: `let value = cast(Int(""), type: (Int?.self)!)` as T.self is an unresolved identifier in that context. This also does the trick with less ?!: `let value = cast(Int(""), type: Optional.self)` – AJ Venturella Aug 28 '15 at 16:28
  • @AdamVenturella Try `(Int?).self` instead since the compiler thinks that the `?` is in this case optional chaining rather than a type. – Qbyte Aug 28 '15 at 16:33
  • You learn something new everyday. I was wondering why it was so awkward to get the type of an optional. I could tell that the compiler thought it needed to be unwrapped, did not even consider `(Int?).self` Thanks for that! – AJ Venturella Aug 28 '15 at 16:35