Home > database >  Return async throwing function from async throwing function
Return async throwing function from async throwing function

Time:01-17

Is it possible to just return an async throwing function from another async throwing function, or do we have to try! await? I'm trying to avoid extra code where I am essentially just "forwarding" on a function call.

// Function I'd like to return
func foo() async throws -> Bool {
    return true
}

// Works, but looking to simplify
func bar() async throws -> Bool {
  return try! await foo()
}

// What I hoped/expected would work, but doesn't
// XCODE: Call can throw but is not marked with 'try'
func bar() async throws -> Bool {
  return foo()
}

// XCODE: Cannot convert return expression of type 'Bool' to return type '() async throws -> Bool'
func bar() async throws -> (() async throws -> Bool) {
    return foo()
}

// Works, but see next example
func bar() async throws -> (() async throws -> Bool) {
    return foo
}

// Does not work, see bar() function below
func baz(_ some: Int) async throws -> Bool {
    return true
}
func bar() async throws -> (() async throws -> Bool) {
    // Xcode nopes this
    return baz(12)
}

Is try! await the best practice and option here? Or is there some other return value from bar() that would allow me to return foo() like in the "what I hoped" example?

CodePudding user response:

The try await form (without !) is correct. It could be simplified slightly to this, since the return is not needed.

func bar() async throws -> Bool { try await foo() }

The try and await are absolutely required at the point of calling the method. They exist to show the programmer that foo may throw and that it is a suspension point (thus there may be reentrance). Requiring them is a significant feature of Swift. If you see foo() without a try, then you know for certain it does not throw. Same for await. If you could drop the try await, then that certainty would be gone.

(There is one place you currently can omit the markers, which is inside an expression that is itself marked with try or await. But that doesn't help with what you're trying to do. The try await must still be within the statement that calls the method.)

To the title of your question, your example shows that it is certainly possible:

func bar() async throws -> (() async throws -> Bool) {
    return foo
}

The correct way to implement your last example is by returning an anonymous function as the signature requires:

func bar() async throws -> (() async throws -> Bool) {
    { try await baz(12) }
}

Again, you cannot avoid the try await at the point that the function is called. This is on purpose. (The compiler doesn't need the try or await; they literally do nothing for the compiler. The programmer needs them in order to reason about what the code does.)

All that said, there is a shortcut to what you're describing. You can just make function-type variables. Those behave (almost) just like functions:

let bar = foo
try await bar()

This is (almost) equivalent to your "return a function" form. (And you would still need a try await for your version that requires a parameter.)

The one sad thing is that Swift does not permit you to use a function-typed variable in order to conform to a protocol that requires a method. That really is IMO just an unfortunate limitation in the language.

The one last thing you can do for methods that take parameters would be to have them return functions as well. For example, this is valid:

func baz(_ some: Int) -> () async throws -> Bool {
   { true }
}
func bar() async throws -> (() async throws -> Bool) {
    baz(12)
}

This is likely not particularly useful, but it is legal.

  • Related