As the documentation says, you only get scoped access to the interior pointer. That means it is not safe to return from the next()
function while keeping the pointer around. If you can use offsets to resume parsing on each call to next()
, that seems like a good solution. So you haven't missed anything, and I don't think there's any better way than the one you've already thought of.
An alternative design could be to have the user pass the body of the loop as a closure to your function:
func forEachStatement(
in sql: String, _ body: (Statement) throws -> Void
) rethrows -> Void {
sql.utf8CString.withUnsafeBufferPointer { buffer in
while let nextStatement = parseStatement(buffer) {
try body(nextStatement)
}
}
}
This way, you can use the interior pointer and provide a callback for each parsed statement without exiting the withUnsafeBufferPointer
closure. This isn't entirely satisfying, of course - you lose control flow for loops (break
, continue
, etc) and it doesn't gel very well with things like sequences or iterators. For instance, to create an Array from those statements, you'd have to go through a dance of creating an empty Array and appending each element individually.
As to why C developers don't face the same problems? Because the lifetime of dynamically allocated memory in C is not guaranteed. In C, you are responsible for ensuring the memory the pointer points to is valid, and if you get it wrong, the behaviour is undefined.
There are a couple of things we could do to make this more pleasing, though:
-
Rather than calling the
body
closure, we could suspend what we're doing (while still inside the closure), return the value to whomever called us, and then resume from that point whenever somebody asks for the next statement. When iteration ends, we'd run to the end and finally exit thewithUnsafeBufferPointer
closure.In other words: we could make this a generator. And rather than calling a closure, we'd want to be
yield
-ing values. Conceptually, it is similar to the way_read
and_modify
coroutines work (although you canyield
multiple times), and IIRC there is interest in adding generators to the language at some point, but no concrete plans right now. -
We could just give you a way to safely escape the pointer - i.e. to guarantee the lifetime of the memory it points to. In principle, a (pointer, owner) pair or deconstructed COW could work to give you a pointer that you can store safely.