Быстрое приведение общего к необязательному с нулевым значением вызывает фатальную ошибку

Используя Swift 2, в моем надуманном примере я преобразовываю String в Int или, точнее, Int или Int?, используя универсальный. В случае, когда Int? должно быть равно нулю, приведение завершится ошибкой с фатальной ошибкой: fatal error: unexpectedly found nil while unwrapping an Optional value

Похоже, что они могут быть похожими/повторяющимися вопросами:

Мой вопрос: как можно выполнить приведение к необязательному параметру, который равен нулю?

Пример:

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()

person AJ Venturella    schedule 26.08.2015    source источник
comment
как можно привести к необязательному значению, равному нулю? Вы не можете. Вопрос не имеет смысла. Нет там там - нечего заливать. Это ноль. Вы можете протестировать его, но вы не можете преобразовать его.   -  person matt    schedule 26.08.2015
comment
Его можно изменить, чтобы он возвращал nil, но тогда компилятор жалуется, что nil несовместим с возвращаемым типом T. Однако в случае, когда T равно Int?, допустимым значением будет nil.   -  person AJ Venturella    schedule 26.08.2015
comment
Вы должны сделать правильный вид nil. Попробуйте вернуть Optional<T>.None.   -  person matt    schedule 26.08.2015
comment
Но я допускаю, что это может не сработать для дженерика. Я видел много проблем, когда общий тип заполнителя должен быть необязательным. Это просто не работает.   -  person matt    schedule 26.08.2015
comment
да, даже если я сделаю это: return Optional<Int>.None as! T он взорвется... Я думаю, что ваш последний комментарий, вероятно, попал в самую точку. Необязательные дженерики проблематичны. Та же фатальная ошибка, fatal error: unexpectedly found nil while unwrapping an Optional value   -  person AJ Venturella    schedule 26.08.2015
comment
Да, я думаю, если вы выполните поиск, вы найдете много вопросов об дженериках, где предполагается, что заполнитель должен быть разрешен как необязательный. Проблема в том, что не существует внутренней связи между необязательным элементом и тем, что он обертывает; Необязательный — это другой тип, тип, который сам по себе является универсальным.   -  person matt    schedule 26.08.2015
comment
У меня есть кое-что, что работает. Я забыл, что необязательный - это NilLiteralConvertible. Поэтому, когда я делаю это gist.github.com/aventurella/dd67b6394c87d5551e74, он не терпит неудачу. В основном обеспечивает ограничение на T where T: NilLiteralConvertible   -  person AJ Venturella    schedule 26.08.2015
comment
Кул-о-рама! Ответьте на свой вопрос, чтобы я мог проголосовать за ваш ответ!   -  person matt    schedule 27.08.2015


Ответы (3)


Вы не можете привести nil к какому-то nil, но вы можете сделать что-то вроде nil, как показывает этот искусственный пример:

    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"))
person matt    schedule 26.08.2015

У меня есть кое-что, что работает.

Я забыл, что Optional это NilLiteralConvertible. Таким образом, мы можем предоставить 2 варианта функции преобразования, и она не подведет. По сути, обеспечивает ограничение на T, где 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()
person AJ Venturella    schedule 26.08.2015
comment
Через 48 часов вы можете принять свой собственный ответ, и вы должны это сделать. Это совершенно нормальное и правильное поведение при переполнении стека, и оно помогает другим. - person matt; 27.08.2015

Обычно кастинг в Swift иногда ведет себя странно, когда вы работаете с универсальными типами. Простым обходным решением было бы сделать общую функцию приведения:

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

Теперь вы можете переписать приведение к:

return cast(Int(string), type: T.self)
person Qbyte    schedule 28.08.2015
comment
Это тоже сработало. В случае Int? мне пришлось сделать это с помощью этой функции приведения: let value = cast(Int(""), type: (Int?.self)!), поскольку T.self является неразрешенным идентификатором в этом контексте. Это тоже помогает с меньшими затратами?!: let value = cast(Int(""), type: Optional<Int>.self) - person AJ Venturella; 28.08.2015
comment
@AdamVenturella Вместо этого попробуйте (Int?).self, поскольку компилятор считает, что ? в данном случае является необязательной цепочкой, а не типом. - person Qbyte; 28.08.2015
comment
Ты узнаешь что-то новое каждый день. Мне было интересно, почему было так неудобно получить тип необязательного. Я мог сказать, что компилятор думал, что его нужно развернуть, даже не подумал (Int?).self Спасибо за это! - person AJ Venturella; 28.08.2015