Swift has a lot of functions that take a closure, and are known to call it exactly once.
Unsafe memory access:
func withUnsafePointer<T, Result>(to value: T, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result
func withUnsafePointer<T, Result>(to value: inout T, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result
func withUnsafeMutablePointer<T, Result>(to value: inout T, _ body: (UnsafeMutablePointer<T>) throws -> Result) rethrows -> Result
func withUnsafeBytes<T, Result>(of value: T, _ body: (UnsafeRawBufferPointer) throws -> Result) rethrows -> Result
func withUnsafeBytes<T, Result>(of value: inout T, _ body: (UnsafeRawBufferPointer) throws -> Result) rethrows -> Result
func withUnsafeMutableBytes<T, Result>(of value: inout T, _ body: (UnsafeMutableRawBufferPointer) throws -> Result) rethrows -> Result
Extending object’s lifetime:
func withExtendedLifetime<T, Result>(_ x: T, _ body: (T) throws -> Result) rethrows -> Result
func withExtendedLifetime<T, Result>(_ x: T, _ body: () throws -> Result) rethrows -> Result
Allow for the use of non-escaping closure in escaping one is required:
func withoutActuallyEscaping<ClosureType, ResultType>(_ closure: ClosureType, do body: (ClosureType) throws -> ResultType) rethrows -> ResultType
And Dispatch
’s synchronization:
func sync<T>(flags: DispatchWorkItemFlags, execute work: () throws -> T) rethrows -> T
Sometimes you want to allocate data inside these closure but compiler won’t let you assign properties across closure boundary without capturing them.
let value: Int
queue.sync {
// error
// We need to capture `value` but value is not initialized.
// Even if it’s initialized, we wouldn’t be able to assign value.
value = 0
}
This can be solved easily since most of these functions allow the closure to return values, and they’ll propagate those return values to the caller. The example then becomes:
let value = queue.sync { () -> Int in
return 0 // ok
}
But this solution isn’t very scalable. If you want to initialize multiple variables, you’ll need to return tuple, and it can lose readability quickly.
let (value1, value2, value3, value4) = queue.sync { () -> (Int, Int, Int, Int) in
// Are we returning items in the right order?
// If there multiple return path, reordering/adding/removing variables will be troublesome.
return (0, 0, 0, 0)
}
So I’d like for these closure to have the added @once
annotation so that compiler can reason about these kind of closure more easily and potentially allow us to do this.
let value1, value2, value3, value4: Int
queue.sync {
// some code
value1 = 0
// some other code
value2 = 0
// ...
value3 = 0
value4 = 0
}
So the @once
closure will be treated much the same way as the do
-block.
What do you guy’s think? Is there any limitation that prevent us from doing this?