Условие Guard провоцирует ошибку компилятора, которая говорит о закрытии

Рассмотрим этот код:

class Foo {
    var a: Int
    var b: Int

    init(a: Int, b: String?) throws {
        self.a = a

        guard self.a > 0 else {
            throw "Too little a!"
        }
        self.b = self.a
    }
}

extension String: Error {}

Довольно бессмысленно, но суть в том, что он прекрасно компилируется. Теперь замените щиток на:

guard b == nil || self.a > 0 else {

Не мы получаем ошибку компилятора!

Ошибка: 'self' захвачено закрытием до инициализации всех членов

Я, например, нигде не вижу закрытия. Компилятор переводит guard условия в замыкания, если они являются составными выражениями, что вводит ошибку (что было бы правильно, если бы было замыкание)?

Баг или фича?

Это с Swift 3.0.2.

1 ответ

Решение

Проблема, как объяснил Мартин в этом вопросе и ответах, состоит в том, что|| оператор реализован с@autoclosure второй параметр, чтобы можно было выполнять оценку короткого замыкания (правое выражение нужно оценивать только в том случае, если левое выражение оценивается как false).

Поэтому в выражении

b == nil || self.a > 0

self.a > 0 неявно обернут в () -> Bool закрытие. Это проблематично, потому что требует захвата self, чтобы a можно получить после применения закрытия.

Однако в инициализаторе Swift жестко ограничивает то, что вы можете делать с self прежде чем он был полностью инициализирован. Одним из таких ограничений является невозможность захвата закрытием - вот почему вы получаете ошибку компилятора.

Хотя на самом деле нет ничего плохого в закрытии { self.a > 0 } отлов self до того, как он полностью инициализирован, потому что все закрытие делает доступ a на нем, который уже был инициализирован. Следовательно, это на самом деле просто крайний случай (для которого есть открытый отчет об ошибках), который, я надеюсь, будет сглажен в будущей версии языка.

До тех пор одно из решений, показанных Мартином в этих вопросах и ответах, заключается в использовании временной локальной переменной для создания копии значения self.aизбегая захвата self в закрытии:

class Foo {
    var a: Int
    var b: Int

    init(a: Int, b: String?) throws {

        // some computation meaning that a != self.a
        self.a = a * 42

        // temporary local variable to be captured by the @autoclosure.
        let _a = self.a
        guard b == nil || _a > 0 else {
            throw "Too little a!"
        }

        self.b = self.a
    }
}

extension String: Error {}

Очевидно, это предполагает, что self.a != aв противном случае вы можете просто обратиться к a вместо self.a в guard состояние.

Другие вопросы по тегам