maxSplits is an initializer argument, while the other variables are already-initialized members. I get an "Constant 'self.cachedStart' captured by a closure before being initialized" on the use of omitEmptySubsequences in the else-if line. If I replace that with the argument it was initialized from (omittingEmptySubsequences), it works. Why? And it doesn't seem universal; uses of already-initialized base, isSeparator, and the first mention of omitEmptySubsequences were OK. The second use was part of a decision, but base was also in that test expression.
struct LazySplitCollection<Base: Collection> {
let base: Base
let maxSplitCount: Int
let omitEmptySubsequences: Bool
let isSeparator: (Base.Element) -> Bool
let cachedStart: Range<Base.Index>?
init(_ base: Base, maxSplits: Int, omittingEmptySubsequences: Bool, whereSeparator isSeparator: @escaping (Base.Element) -> Bool) {
precondition(maxSplits >= 0)
self.base = base
maxSplitCount = maxSplits
omitEmptySubsequences = omittingEmptySubsequences
self.isSeparator = isSeparator
if maxSplits > 0 {
cachedStart = self.base.firstSplitRange(omittingEmptySubsequences: omitEmptySubsequences, whereSeparator: self.isSeparator)
} else if !self.base.isEmpty || !omittingEmptySubsequences {
cachedStart = self.base.startIndex..<self.base.endIndex
} else {
cachedStart = nil
}
}
}
The state of the else-if line is after the fix. Changing the last term back to omitEmptySubsequences should bring back the error. Collection.firstSplitRange is a custom extension method.
Okay I found the issue. The error is actually correct and it's even emitted at the right place. You are indeed capturing self before the last value could be initialized properly.
This feature was supposed to copy (Objective-)C(++)'s short-circuit behavior on && and ||. Those languages make them work by fiat with compiler magic. We actually have implementations based on a language feature. But not using magic has consequences; anything that can't handle (auto-)closures will choke. I just tried the original code, but swapped the omit and base terms, and sure enough, the error returned on base (which is now the second term, i.e. the one under autoclosure.)
I just tried another workaround. For certain tests, we can use a comma as an AND term. Further, it's a built-in, so it uses compiler magic and not an @autoclosure, and so all terms can use already-initialized members. But remember your Computer Science rules on switching between OR and AND chains: