Условие 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
состояние.