ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [SE-0493] defer support async
    Swift 2026. 1. 24. 12:54

    안녕하세요. 그린입니다 🍏

    이번 포스팅에서는 SE-0493 Support async calls in defer bodies에 대해 정리해보겠습니다 🙋🏻


    Intro

    Swift의 defer는 정말 유용한 기능이죠.

    scope를 벗어날 때 반드시 실행되어야 하는 cleanup 코드를 깔끔하게 작성할 수 있게 해줍니다.

     

    그런데 문제는 defer 블록 안에서는 async 함수를 호출할 수 없다는 점입니다.

    async context에 있어도 await를 쓰면 에러가 나거든요.

     

    이번 SE-0493은 바로 이 제약을 풀어주는 제안입니다. 이미 Accepted 상태이고 구현도 완료되었으니, 곧 실무에서도 쓸 수 있을 것 같습니다 🚀

     


    왜 필요한가?

    defer는 Swift 2에서 도입된 기능으로, scope 기반 cleanup을 안정적으로 수행할 수 있게 해줍니다.

    func sendLog(_ message: String) async throws {
      let localLog = FileHandle("log.txt")
      
      // Will be executed even if we throw
      defer { localLog.close() }
      
      localLog.appendLine(message)
      try await sendNetworkLog(message)
    }

     

    setup 코드와 cleanup 코드를 가까이 둘 수 있고, 모든 exit path에 cleanup을 수동으로 넣을 필요가 없어서 편리합니다.

     

    하지만 async는 안 됐습니다

    문제는 defer 블록 안에서는 async 작업을 할 수 없다는 겁니다.

    func f() async {
      await setUp()
      // error: 'async' call cannot occur in a defer body
      defer { await performAsyncTeardown() }
      
      try doSomething()
    }

     

    async cleanup이 필요한 경우엔 딱히 좋은 방법이 없었어요.

    • 모든 exit path에 수동으로 cleanup 코드를 넣거나 (버그 위험)
    • Task를 새로 만들어서 "언젠가" 실행되도록 하거나
    defer {
      // We'll clean this up... eventually
      Task { await performAsyncTeardown() }
    }

     

    두 방법 다 별로죠 🥲

     


    제안된 해결책

    이제 defer 블록 안에서 await를 쓸 수 있습니다.

    func f() async {
      await setUp()
      defer { await performAsyncTeardown() } // OK
      
      try doSomething()
    }

     

    scope를 벗어날 때 defer 블록들이 역순으로 실행되는 건 동일하고, async 작업이 있으면 자동으로 await됩니다.

    완료될 때까지 기다린 후에 함수가 반환되는 거죠.

     


    어떻게 동작하나?

    async 작업이 포함된 defer는 scope exit 시점에 암묵적으로 await됩니다.

    단, 당연히 부모 context가 async여야 합니다.

    func f() {
      // error: 'async' call in a function that does not support concurrency
      defer { await g() }
    }

     

    async 추론

    closure 같이 async를 추론할 수 있는 경우, defer 안의 await만으로도 충분히 추론됩니다.

    // 'f' implicitly has type '() async -> ()'
    let f = {
      defer { await g() }
    }

     

    Isolation 상속

    defer 블록은 항상 둘러싼 scope의 isolation을 상속받습니다.

    따라서 async defer가 추가적인 suspension point를 만들지는 않아요. 내부에서 호출하는 함수가 만드는 suspension point만 있을 뿐이죠.

     


    호환성

    Source compatibility

    완전히 additive하고 opt-in입니다.

    기존 코드는 defer 안에서 async 작업을 못 했으니, 기존 동작이 바뀔 일은 없습니다.

     

    ABI compatibility

    ABI 레벨에서는 아무 영향이 없습니다. 순전히 구현 디테일이에요.

     


    대안들

    defer async 같은 명시적 표시 필요?

    이런 식으로 쓰도록 요구할 수도 있었습니다.

    defer async {
      await fd.close()
    }

     

    하지만 제안에서는 거부했어요.

    defer 블록은 보통 작고 타겟팅된 cleanup 작업이라서, 추가적인 마커가 크게 명확성을 높이지 않는다고 봤습니다.

    게다가 defer를 둘러싼 context는 이미 async여야 하고, 함수 선언이라면 명시적일 거고, closure라면 이미 await로 추론되는 거니까요.

     

    await만으로 충분하다는 판단입니다.

    Scope exit에 명시적 await 필요?

    async defer가 암묵적인 suspension point를 만들 수 있다는 우려도 있었습니다.

    하지만 이건 async let의 분석과 거의 동일합니다.

     

    오히려 defer가 더 나은 게, async let은 소스 어디에도 await가 없을 수 있지만, defer는 블록 안에 항상 await가 있으니까요.

     

    defer 안에서 task cancellation 무시?

    토론 중에 나온 또 다른 의견은, defer 안에서는 현재 task의 cancellation 상태를 관찰하지 못하게 해야 하는 거 아니냐는 거였어요.

     

    왜냐면 task cancellation은 코드를 스킵할 수 있는데, cleanup 코드는 반드시 실행되어야 하니까요.

     

    예를 들어 timeout이나 HTTP 요청을 cleanup에 쓰는 경우, cancelled task에서 실행되면 "작업을 하지 말라"는 신호로 해석될 수 있습니다.

    그래서 defer 안에서는 Task.isCancelled를 항상 false로 만들자는 제안이었죠.

     

    하지만 채택하지 않았습니다

    이유는 아래와 같아요.

    1. 동기 defer도 이미 cancellation을 관찰합니다. 
      defer 안에서 Task.isCancelled를 확인하면 실제 취소 상태를 봅니다. 
      await 하나 추가했다고 동작이 바뀌는 건 바람직하지 않습니다.
    2. 기존 코드를 defer로 옮긴다고 동작이 바뀌면 안 됩니다. 
      cancellation에 민감한 API는 이미 일반 코드에서도 조심해서 써야 하고 (언제든 취소될 수 있으니까), 이건 withCancellationIgnored { ... } 같은 범용 기능으로 해결하는 게 맞다고 봤습니다.
    실제로 SE-0504 Task Cancellation Shields가 바로 이 문제를 해결하는 제안이죠!

     


    Conclusion

    SE-0493은 이미 Accepted되었고 구현도 완료된 상태입니다.

    곧 머지되지 않을까 싶어요!

    async cleanup이 필요한 경우가 생각보다 많은데, 지금까지는 Task { ... } 같은 꼼수를 써야 했거든요.

    이제 defer를 원래 의도대로 사용할 수 있게 되어서 코드가 훨씬 깔끔해질 것 같습니다.

    특히 SE-0504 Task Cancellation Shields와 함께 쓰면 진짜 강력할 것 같네요 😃

     


    References

     

    swift-evolution/proposals/0493-defer-async.md at main · swiftlang/swift-evolution

    This maintains proposals for changes and user-visible enhancements to the Swift Programming Language. - swiftlang/swift-evolution

    github.com

     

    SE-0493: Support `async` calls in `defer` bodies

    Hello, Swift community! The review of SE-0493: Support async calls in defer bodies begins now and runs through October 6, 2025. Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if y

    forums.swift.org

Designed by Tistory.