Search code examples
swiftswift-concurrency

Why Actor-isolated property cannot be passed 'inout' to 'async' function call?


Considering below dummy codes:


@MainActor var globalNumber = 0

@MainActor
func increase(_ number: inout Int) async {
    // some async code excluded
    number += 1
}

class Dummy: @unchecked Sendable {
    @MainActor var number: Int {
       get { globalNumber }
       set { globalNumber = newValue }
    }

    @MainActor
    func change() async {
       await increase(&number) //Actor-isolated property 'number' cannot be passed 'inout' to 'async' function call
    }
}

I'm not really trying to make an increasing function like that, this is just an example to make everything happen. As for why number is a computed property, this is to trigger the actor-isolated condition (otherwise, if the property is stored and is a value type, this condition will not be triggered).

Under these conditions, in function change(), I got the error: Actor-isolated property 'number' cannot be passed 'inout' to 'async' function call.

My question is: Why Actor-isolated property cannot be passed 'inout' to 'async' function call? What is the purpose of this design? If this were allowed, what problems might it cause?


Update 2025-2-28

Could it be because of the reentrancy? Because the increase function is an explicit async function, there may be a suspension point inside it, which will cause reentrancy. This is just my guess, but I still can't come to my conclusion well.


Solution

  • The issue is a combination of two factors:

    1. An inout parameter of an async method employs “copy-in copy-out” semantics. As The Swift Programming Language says:

      In-out parameters are passed as follows:

      1. When the function is called, the value of the argument is copied.
      2. In the body of the function, the copy is modified.
      3. When the function returns, the copy’s value is assigned to the original argument.

      This behavior is known as copy-in copy-out or call by value result. For example, when a computed property or a property with observers is passed as an in-out parameter, its getter is called as part of the function call and its setter is called as part of the function return.

    2. An async method can have an await whereby, as you noted, actor reentrancy comes into play.

    The combination of these two factors means that if the compiler permitted async method with the inout parameter, it would be possible for the copied value of the inout parameter to not reflect concurrent changes (during reentrancy) to the original actor-isolated property.