Property wrapper that defines a task-local value key.

    @propertyWrapper final class TaskLocal<Value> where Value : Sendable

    A task-local value is a value that can be bound and read in the context of a Task. It is implicitly carried with the task, and is accessible by any child tasks the task creates (such as TaskGroup or async let created tasks).

    Task-local declarations

    Task locals must be declared as static properties (or global properties, once property wrappers support these), like this:

    enum TracingExample {
        static let traceID: TraceID?

    Default values

    Task local values of optional types default to nil. It is possible to define not-optional task-local values, and an explicit default value must then be defined instead.

    The default value is returned whenever the task-local is read from a context which either: has no task available to read the value from (e.g. a synchronous function, called without any asynchronous function in its call stack), or no value was bound within the scope of the current task or any of its parent tasks.

    Reading task-local values

    Reading task local values is simple and looks the same as-if reading a normal static property:

    guard let traceID = TracingExample.traceID else {
      print("no trace id")

    It is possible to perform task-local value reads from either asynchronous or synchronous functions. Within asynchronous functions, as a “current” task is always guaranteed to exist, this will perform the lookup in the task local context.

    A lookup made from the context of a synchronous function, that is not called from an asynchronous function (!), will immediately return the task-local’s default value.

    Binding task-local values

    Task local values cannot be set directly and must instead be bound using the scoped $traceID.withValue() { ... } operation. The value is only bound for the duration of that scope, and is available to any child tasks which are created within that scope.

    Detached tasks do not inherit task-local values, however tasks created using the Task { ... } initializer do inherit task-locals by copying them to the new asynchronous task, even though it is an un-structured task.


    static var traceID: TraceID?
    print("traceID: \(traceID)") // traceID: nil
    $traceID.withValue(1234) { // bind the value
      print("traceID: \(traceID)") // traceID: 1234
      call() // traceID: 1234
      Task { // unstructured tasks do inherit task locals by copying
        call() // traceID: 1234
      Task.detached { // detached tasks do not inherit task-local values
        call() // traceID: nil
    func call() {
      print("traceID: \(traceID)") // 1234

    This type must be a class so it has a stable identity, that is used as key value for lookups in the task local storage.

    Citizens in _Concurrency