Reputation: 4912
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
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
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
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