import Foundation
actor FooManager {
func performActionA() -> Int{
return 1
}
}
struct FooClient {
var actionA: () async -> Int
}
public func test() async -> Int{
let manager = FooManager()
return manager.performActionA()
}
struct SomeStruct{
public static let someProperty: FooClient = {
let manager = FooManager()
let closure = {() async -> Int in
return manager.performActionA()
}
// return FooClient(actionA: test)
return FooClient(actionA: closure)
}()
}
I assume initializing FooClient with a closure in the same block should do the same as initializing FooClient using a normal function?
For test(), it gives an error "Expression is 'async' but is not marked with 'await'"
For the closure within someProperty, it's fine even though I don't use await? (actually if I use await here, it gives an error: "No 'async' operations occur within 'await' expression")
This is on Xcode 16, and on Xcode 15 I need to use await for both, otherwise compiler complains "Expression is 'async' but is not marked with 'await'".
Have no idea why this is the case. I think using test() should be not so different with using a closure? Also isn't methods on actor async by default? Why in the closure I can't await for an async method call...?
Edit: I'm using Swift 5 mode, and with Swift 6 mode as @vns said it needs to use @Sendable to make it compile.
Edit: actually it's a warning not an error (I forget my project has a flag that tells compiler to treat warning as error)
That won't give an answer, but with Swift 6 mode, this code won't compile at all currently. someProperty is a global and should be either isolated to global actor or FooClient should be Sendable. Then, it will also require actionA to be @Sendable. So it will look like:
actor FooManager {
func performActionA() -> Int{
return 1
}
}
struct FooClient: Sendable {
var actionA: @Sendable () async -> Int
}
public func test() async -> Int {
let manager = FooManager()
return await manager.performActionA()
}
struct SomeStruct{
public static let someProperty: FooClient = {
let manager = FooManager()
let closure = { @Sendable () async -> Int in
return manager.performActionA()
}
return FooClient(actionA: closure)
}()
}
As for not requiring await here – note that it happens only inside { /* ... */ }(), moving it to a static function will again make it to force await – I don't have an answer why that's behaves in this way, as it for some reason makes this closure implicitly isolated (?) to the actor, and according to SIL it actually hops to actor's executor despite missing await:
// closure #1 in closure #1 in variable initialization expression of static SomeStruct.someProperty
sil private @$s3App10SomeStructV12somePropertyAA9FooClientVvpZfiAFyXEfU_SiyYaYbcfU_ : $@convention(thin) @Sendable @async (@guaranteed FooManager) -> Int {
// %0 "manager" // users: %4, %3, %2, %1
bb0(%0 : @closureCapture $FooManager):
debug_value %0 : $FooManager, let, name "manager", argno 1 // id: %1
%2 = class_method %0 : $FooManager, #FooManager.performActionA : (isolated FooManager) -> () -> Int, $@convention(method) (@sil_isolated @guaranteed FooManager) -> Int // user: %4
hop_to_executor %0 : $FooManager // id: %3
%4 = apply %2(%0) : $@convention(method) (@sil_isolated @guaranteed FooManager) -> Int // user: %5
return %4 : $Int // id: %5
} // end sil function '$s3App10SomeStructV12somePropertyAA9FooClientVvpZfiAFyXEfU_SiyYaYbcfU_'
As this behaviour only happens inside { /* ... */ }() block on a static or member property, that seems like an issue in the compiler. For example, this will give an error:
extension SomeStruct {
static func makeClient() -> FooClient {
let client = {
let manager = FooManager()
let closure = { @Sendable () async -> Int in
return manager.performActionA() // error: Expression is 'async' but is not marked with 'await'
}
return FooClient(actionA: closure)
}()
return client
}
}
Sorry I just checked, it's actually a warning instead of an error (I forgot I had -warnings-as-errors set ). I also checked SIL and LLVM IR and I don't see too much difference except for a @sil_isolated in SIL. I think maybe it's just a bug in swift compiler's type checking, but it still can generate the correct compile result?
I didn't understand what's the purpose of @sil_isolated to be honest, as I noticed it for the first time and docs on it for me just words and raise too much questions (e.g. what drives optionality of marking?):
SIL functions may optionally mark a function parameter as @sil_isolated. An @sil_isolated parameter must be one of:
An actor or any actor type.
A generic type that conforms to Actor or AnyActor.
and must be the actor instance that a function is isolated to. Importantly this means that global actor isolated nominal types are never @sil_isolated. Only one parameter can ever be marked as @sil_isolated since a function cannot be isolated to multiple actors at the same time
It doesn't seem to be present only in that case, test function has it as well, so maybe that is the new default for actor-isolated calls in Swift 6.