Выйти рано, используя `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 вместо этого создать свой объект (что является лучшим стилем ИМХО, так как моя философия заключается в том, что инициализаторы, как правило, должны только гарантировать, что объект самосогласован и что согласованность с некоторым другим состоянием должна быть обеспечена на другом уровне).
Другие вопросы по тегам