Выйти рано, используя `guard` без ввода`return` - возможно только при использовании `Never`?
Этот вопрос вызван любопытством по поводу того, что Swift не является предложением о создании подобного кода.
Стрижи guard
Заявление офигенно для ранних выходов. В некоторых сценариях мы можем захотеть выполнить один вызов помимо выхода с помощью return
,
final class AppCoordinator {
func showApplePaySplash() -> Void { /* some presentation logic */ }
}
final class OnboardingCoordinator {
init(settings: Settings, parent: AppCoordinator) {
// This code should probably use a `switch` statement and not `guard`, but I am curious about this
guard settings.hasSeenApplePaySplash else {
parent.showApplePaySplash() // method returning `Void`
return
}
// Some more logic...
}
}
Что меня интересует, так это возможность сократить синтаксис:
guard settings.hasSeenApplePaySplash else {
parent.showApplePaySplash()
return
}
Так как это внутри init
мы не можем написать:
guard settings.hasSeenApplePaySplash else {
return parent.showApplePaySplash() // compilation error: `'nil' is the only return value permitted in an initializer`
}
Конечно, мы можем изменить четыре строки на этот oneliner:
guard settings.hasSeenApplePaySplash else { parent.showApplePaySplash(); return }
Который довольно неплохо зачитывает ИМХО. Но я все еще хотел бы избавиться от этого return
(потому что мне любопытно, если это возможно. Нет необходимости говорить мне: "просто используйте return man").
В этом другом сценарии, где мы хотели бы guard
против некоторого неопределенного плохого поведения / состояния:
guard index < myArray.count else { fatalError("Array out of bounds exception, did you think about X, Y, Z?") }
Нам не нужно писать return
, так как метод fatalError
возвращает конкретный тип с именем Never
,
Код ниже этой точки просто экспериментальный, движимый любопытством, так как это плохой код Swift:
Так что, если бы мы могли изменить подпись:
func showApplePaySplash() -> Void
использовать Never
, вот так:
func showApplePaySplash() -> Never
Тогда мы могли бы заменить:
guard settings.hasSeenApplePaySplash else { parent.showApplePaySplash(); return }
Это то, что мне любопытно, еще раз, не предпочтение или одобрение:
С просто:
guard settings.hasSeenApplePaySplash else { parent.showApplePaySplash() }
Но Never
не имеет инициализатора. И, похоже, единственная возможность создания Never
это использовать такие методы, как fatalError
создать крах.
Я нашел этот отличный SO ответ от @guy-daher - что позволяет заменить fatalError
позволяя "поймать" его в тестах. Но он использует waitForExpectations(timeout: 0.1)
что невозможно за пределами тестовых пакетов?
Так Never
вероятно, здесь не поможет. До Свифта 4 (до Свифта 3?) Была аннотация функции под названием @noreturn
казалось, что это могло бы помочь?
Есть ли способ добиться этого?:)
Редактировать:
Добавлен отказ от ответственности, что это плохая идея, пожалуйста, не продолжайте преуменьшать мой вопрос, вызванный любопытством.
2 ответа
Почему бы не использовать defer для указания кода очистки выхода?
final class OnboardingCoordinator {
init(settings: Settings, parent: AppCoordinator) {
defer {
parent.showApplePaySplash() // method returning `Void`
}
guard settings.hasSeenApplePaySplash else {
return
}
// Some more logic...
}
}
Never
это новый @noreturn
, а также @noreturn
означало, что выполнение буквально не может продолжаться после возврата функции. Точка Never
это именно то, что это необитаемый тип и что невозможно создать экземпляр этого.
Never
(а также @noreturn
перед этим) имеют особое значение для компилятора: когда вы вызываете функцию, которая "никогда не возвращается", компилятору не нужно предполагать, что после вызова функции есть допустимый путь к коду, и он может выполнять оптимизацию, предполагая, что код будет никогда не будет казнен. На практике LLVM добавляет инструкцию прерывания (например, ud2
на x86) после вызова, чтобы убедиться, что программа аварийно завершает работу, если функция действительно возвращается.
Вы можете сделать одно из следующего:
- менять
init?
вinit(...) throws
, делатьparent.showApplePaySplash()
вернутьError
и использоватьthrow parent.showApplePaySplash()
вguard
оговорка; - помирись с
init
быть особенным и вернутьсяnil
; - делать
init
частный, и использоватьclass func
вместо этого создать свой объект (что является лучшим стилем ИМХО, так как моя философия заключается в том, что инициализаторы, как правило, должны только гарантировать, что объект самосогласован и что согласованность с некоторым другим состоянием должна быть обеспечена на другом уровне).