Why Task { } doesn't need explicit use of self?

Hi,

Question

  • Why doesn't unstructured task Task { } require explicit use of self?

My understanding (could be wrong):

  • Both unstructured task Task { ... } and detached task Task.detached { ... } both run asynchronously and may outlive the function from which it is called.
  • Both could cause data race when used in non-isolated function

Code

class Car {
    var price = 100
    
    func f1() {
        Task {
            price = price + 1 //Why doesn't property `price` not require explicit of use of self? wouldn't this run asynchronously?
        }

        Task.detached {
//            price = price + 1 //Error: Reference to property 'price' in closure requires explicit use of 'self' to make capture semantics explicit
        }
    }
}
3 Likes

The closure is marked with @_implicitSelfCapture with the idea that the task will eventually complete, releasing captured objects. Some question that here Explicit self not required for Task closures?

1 Like

Thanks @paiv I have some doubts, please bear with me

Doubts:

  1. I couldn't find @_implicitSelfCapture in the documentation, should I be looking else where?
  1. Wouldn't the detached task also eventually complete?

You won't find @_implicitSelfCapture in the documentation because it's private (hence the underscore prefix).

1 Like

Thanks @Avi, I wish it was mentioned in the documentation that self is implicitly captured, because looking at @escaping closure it wasn't obvious.

1 Like

Docs says

The intent behind requiring self. when capturing self in an escaping closure is to warn the developer about potential reference cycles. The closure passed to Task is executed immediately, and the only reference to self is what occurs in the body. Therefore, the explicit self. isn't communicating useful information and should not be required.

But next here is:

Note: The same applies to the closure passed to Task.detached and TaskGroup.addTask .

But detached Task closure is escaping
operation: @escaping @Sendable () async -> Success

I think the documentation in the footnote is outdated

1 Like

Thanks @AlexSedykh for the link

I think detached task initially wasn't required to explicitly mention self but was later changed.

Questions

  1. Does detached task have a self requirement because of how it interacts with actors (it doesn't inherit the context and could run on a different executor)?
  2. Is there a way to reliably detect data races? TSAN sometimes catches it and sometimes seems to miss data races. (not sure others face this issue or I am missing something)