A problem I frequently run into: TaskLocal values do not propagate into escaping closures (except unstructured tasks).
@TaskLocal var requestID = 0
$requestID.withValue(1) {
print(requestID) // 1
someMethodWithEscapingClosure {
print(requestID) // 0
}
}
I'd love for there to be some way to propagate all TaskLocal values into an escaping closure. I'm not sure what the API would look like, but one idea:
$requestID.withValue(1) { withTaskLocals in
print(requestID) // 1
someMethodWithEscapingClosure {
print(requestID) // 0
withTaskLocals {
print(requestID) // 1
}
}
}
So, making an overload of withValue that provides a closure with which the current TaskLocal value. However, if it's on the individual withValue, then it obviously wouldn't compose well with other TaskLocals. Another idea...
@TaskLocal var taskLocalA = 0
@TaskLocal var taskLocalB = 1
$taskLocalA.withValue(37) {
$taskLocalB.withValue(42) {
print(taskLocalA, taskLocalB) // 37, 42
captureTaskLocals { withTaskLocals in
print(taskLocalA, taskLocalB) // 37, 42
methodWithEscapingClosure {
print(taskLocalA, taskLocalB) // 0, 1
withTaskLocals {
print(taskLocalA, taskLocalB) // 37, 42
}
}
}
}
}
This is getting pretty nuts with the levels of nesting though. Ideally there'd be some way to propagate it without introducing further nesting. Maybe using ~Escapable values?
struct TaskLocalScopeFactory {
func callAsFunction() -> TaskLocalScope
}
struct TaskLocalScope: ~Escapable, ~Copyable {}
@TaskLocal var taskLocalA = 0
@TaskLocal var taskLocalB = 1
$taskLocalA.withValue(37) {
$taskLocalB.withValue(42) {
print(taskLocalA, taskLocalB) // 37, 42
captureTaskLocals { makeScope in
print(taskLocalA, taskLocalB) // 37, 42
methodWithEscapingClosure {
print(taskLocalA, taskLocalB) // 0, 1
let scope = makeScope()
print(taskLocalA, taskLocalB) // 37, 42
// automatically closes scope
}
}
}
}
Is this feasible to implement? Is it advisable? Is there a better way to do this?