ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Swift 컴파일러의 타입 추론 파헤치기 (feat. 왜 이렇게 컴파일이 오래 걸릴까?)
    Swift 2025. 7. 27. 09:35

    안녕하세요. 그린입니다 🍏
    이번 포스팅은 Swift 컴파일러의 타입 추론 과정에 대해 깊이 있게 알아보겠습니다 🙋🏻


    Swift Type Inference Deep Dive

    Swift 개발을 하다 보면 한 번쯤 마주치는 상황이 있죠.
    let result = data
        .map { $0.someProperty }
        .flatMap { $0.transform() }
        .compactMap { $0.process() }

     

    이런 코드를 작성하고 빌드를 돌렸는데...
    컴파일러가 몇 분씩 돌아가거나 아예 "expression was too complex" 에러를 뱉어내는 경우 말이에요 😅

     

    왜 이런 일이 일어나는 걸까요?
    그 답은 Swift의 강력하지만 복잡한 타입 추론 시스템에 있습니다.

     


    Why Type Inference Matters?

    Swift의 가장 큰 매력 중 하나는 바로 타입 추론이에요.
    let numbers = [1, 2, 3] // [Int]로 자동 추론
    let name = "Swift" // String으로 자동 추론

     

    개발자가 일일이 타입을 명시하지 않아도 컴파일러가 알아서 적절한 타입을 찾아주죠.
    이게 Swift 코드를 간결하고 읽기 쉽게 만들어주는 핵심 기능입니다.

     

    하지만 이 편리함 뒤에는 복잡한 알고리즘이 숨어있어요.
    특히 제네릭이나 복잡한 표현식이 포함되면 컴파일러는 정말 많은 일을 해야 합니다.


    Swift의 제약 기반 타입 시스템

    Swift는 전통적인 Hindley-Milner 알고리즘에서 영감을 받았지만, 실제로는 제약 기반 타입 체커를 사용합니다.

    이 시스템의 핵심 개념들을 살펴볼께요.

     

    1️⃣ 타입 변수 (Type Variables)

    let value = someFunction() // $T0

    컴파일러는 아직 결정되지 않은 타입을 $T0, $T1 같은 타입 변수로 표현해요.

     

    2️⃣ 제약 조건 (Constraints)

    func process<T: Equatable>(_ value: T) -> T { ... }
    let result = process(42)

    여기서 컴파일러는 다음과 같은 제약들을 생성합니다:

    • $T0Equatable을 준수해야 함
    • 42의 타입과 $T0이 같아야 함
    • 반환 타입도 $T0이어야 함

    3️⃣ 제약 해결 (Constraint Solving)

    모든 제약 조건을 만족하는 구체적인 타입을 찾는 과정이에요.

     

    컴파일 시간이 오래 걸리는 이유들

    🔥 지수적 탐색 공간

    문제가 되는 코드의 대표적인 예시

    // 😱 이런 코드는 피하세요
    let result = a + b * c + d * e + f * g + h * i

     

    각 연산마다 여러 오버로드가 있고, 컴파일러는 가능한 모든 조합을 시도해야 합니다.
    연산자가 늘어날수록 가능한 조합의 수는 기하급수적으로 증가해요.

     

    🔥 복잡한 제네릭 제약

    func complexOperation<P1, P2, C>(
        _ processor1: P1,
        _ processor2: P2,
        _ combiner: C
    ) -> P1.Output 
    where 
        P1: Processable,
        P2: Processable,
        C: Combinable,
        P1.Input == P2.Input,
        P1.Output == P2.Output,
        C.Element == P1.Output
    {
        // 구현...
    }

     

    이런 복잡한 제약들이 서로 얽히면 컴파일러가 해결해야 할 문제가 매우 복잡해져요.

     

    🔥 오버로드 해결

    func transform<T>(_ value: T) -> String { ... }
    func transform<T>(_ value: T) -> Int { ... }
    func transform<T: Numeric>(_ value: T) -> T { ... }
    
    // 컴파일러는 어떤 transform을 선택해야 할까요?
    let result = data.map(transform)

     


    실제 성능 측정해보기

    컴파일 시간을 측정하는 실제 방법들을 소개할께요.

     

    Build Settings에서 측정

    Xcode의 "Other Swift Flags"에 다음을 추가합니다.

     
    -Xfrontend -debug-time-function-bodies
    -Xfrontend -debug-time-expression-type-checking
    -Xfrontend -warn-long-function-bodies=100
    -Xfrontend -warn-long-expression-type-checking=100

     

    커맨드라인에서 측정

    xcodebuild -project 'YourProject.xcodeproj' \
      -scheme 'YourScheme' \
      -configuration 'Debug' \
      clean build \
      OTHER_SWIFT_FLAGS="-Xfrontend -debug-time-expression-type-checking"

    이렇게 하면 어떤 함수나 표현식이 컴파일 시간을 잡아먹는지 정확히 알 수 있어요!

     


    Solutions

    1️⃣ 명시적 타입 주석 활용

    // 😱 Before: 컴파일러가 추론하기 어려운 코드
    let result = data
        .map { $0.process() }
        .flatMap { $0.validate() }
        .compactMap { $0.transform() }
    
    // 😊 After: 중간 타입을 명시
    let processed: [ProcessedData] = data.map { $0.process() }
    let validated: [ValidatedData] = processed.flatMap { $0.validate() }
    let result: [TransformedData] = validated.compactMap { $0.transform() }

     

    2️⃣ 복잡한 표현식 분해

    // 😱 Before: 한 줄에 모든 것을 처리
    let url = "http://" + username + ":" + password + "@" + address + "/api/"
    
    // 😊 After: 단계별로 분해
    let credentials = username + ":" + password
    let host = credentials + "@" + address
    let url = "http://" + host + "/api/"

     

    3️⃣ 타입 힌트 제공

    // 😱 컴파일러가 헷갈리는 경우
    let result = ambiguousFunction()
    
    // 😊 명확한 타입 지정
    let result: String = ambiguousFunction()
    // 또는
    let result = ambiguousFunction() as String

     

    4️⃣ typealias 활용

    // 복잡한 타입을 단순화
    typealias ProcessableCollection<Element: Comparable & Hashable> = 
        Collection where Element == Element
    
    func process<T: ProcessableCollection>(_ item: T) -> T {
        // 깔끔한 인터페이스
    }

     


    Performance Tips & Tricks

    🚀 컴파일러 힌트 활용

    // 컴파일 시간 측정을 위한 플래그들
    OTHER_SWIFT_FLAGS="-driver-time-compilation"
    
    // 제약 해결 임계값 설정
    -Xfrontend -solver-expression-time-threshold=10

     

    🚀 조건부 컴파일 활용

    #if DEBUG
    // 디버그 모드에서는 타입 체킹을 단순화
    typealias ProcessorType = AnyProcessor<String, String>
    #else
    // 릴리즈 모드에서는 최적화된 구체 타입 사용
    typealias ProcessorType = OptimizedStringProcessor
    #endif

     

    🚀 타입 지우기 패턴

    // 복잡한 제네릭 타입을 단순화
    struct AnyProcessor<Input, Output> {
        private let _process: (Input) -> Output
        
        init<P: Processor>(_ processor: P) where P.Input == Input, P.Output == Output {
            self._process = processor.process
        }
        
        func process(_ input: Input) -> Output {
            return _process(input)
        }
    }

     


    컴파일러의 지속적인 개선

    Swift 컴파일러는 계속해서 발전하고 있어요.

     

    주요 개선 방향들

    • 증분 타입 체킹: 변경된 부분만 다시 체킹
    • 병렬 컴파일 최적화: 멀티코어 활용
    • 더 나은 진단 정보: 이해하기 쉬운 오류 메시지

    특히 최근 Swift Summer of Code에서 타입 추론 디버그 출력이 크게 개선되었다고 해요!

     


    Conclusion

    Swift의 타입 추론 시스템은 정말 강력하지만, 그만큼 복잡하기도 합니다.

    핵심은 컴파일러가 어떻게 동작하는지 이해하고, 적절한 타입 힌트와 코드 구조화를 통해 타입 추론의 복잡도를 관리하는 것이에요.

     

    실무에서는 컴파일 시간과 코드의 표현력 사이의 균형을 찾는 것이 중요합니다.
    성능이 중요한 부분에서는 명시적 타입 주석을 활용해보면 어떨까요?

     

    Swift의 제약 기반 타입 체커는 계속해서 개선되고 있고, 우리 개발자들은 이러한 도구들을 효과적으로 활용하여 더 나은 개발 경험을 만들어갈 수 있을 거예요 😃

     

    ⚠️ 이 글에서 소개한 컴파일러 플래그들은 공식적으로 지원되지 않는 내부 디버깅 도구입니다.

    실제 프로덕션에서는 주의해서 사용하세요!

     


    References

     

    Exponential time complexity in the Swift type checker

    One of the most annoying problems in Swift is when the compiler gives an 'Expression was too complex to be solved in a reasonable time' error. I look at why this error occurs, how to avoid it and talk about how this should be solved in a future compiler up

    www.cocoawithlove.com

     

    Swift Summer of Code 2022 Summary

    Google Summer of Code (also known as GSoC) is a long-running mentorship program focused on introducing contributors to the world of open source development. This year marks the fifth time the Swift project has participated in GSoC.

    www.swift.org

     

    New Diagnostic Architecture Overview

    Diagnostics play a very important role in a programming language experience. It’s vital for developer productivity that the compiler can produce proper guidance in any situation, especially incomplete or invalid code.

    www.swift.org

     

    swift/docs/TypeChecker.md at main · swiftlang/swift

    The Swift Programming Language. Contribute to swiftlang/swift development by creating an account on GitHub.

    github.com

    'Swift' 카테고리의 다른 글

    Make DSL with ResultBuilder  (4) 2025.08.30
    Swift Phantom Types  (3) 2025.08.15
    Swift Homomorphic Encryption  (1) 2025.07.05
    Migrating the Password Monitoring service from Java  (3) 2025.06.29
    defer (async throwing contexts)  (1) 2025.05.30
Designed by Tistory.