본문으로 건너뛰기
0
r/jeffnews HN 약 5분

Go 1.26의 타입 생성(Type Construction)과 순환 감지(Cycle Detection) 개선

backend

요약

Go 1.26에서 타입 체커의 타입 생성 알고리즘을 개선해 재귀 타입과 배열 크기 계산 시 발생하던 순환 감지 문제를 체계적으로 해결했다. 불완전한 값이 다운스트림으로 퍼지기 전에 업스트림에서 차단하는 새로운 접근법으로 여러 컴파일러 패닉을 수정.

기사 전체 정리

Go 1.26의 타입 생성(Type Construction)과 순환 감지(Cycle Detection) 개선

  • Go 컴파일러에서 타입 체커가 AST를 순회하면서 각 타입의 내부 표현을 구성하는 과정을 **타입 생성(type construction)**이라고 함. Go 1.26에서 이 부분을 상당히 개선했는데, 사용자 관점에서는 변화가 거의 없지만 미래 개선을 위한 기반 작업임

기본 타입 생성 과정

  • type T []Utype U *int 같은 단순 선언의 경우: T를 만나면 Defined 구조체를 생성하고, 아직 평가 안 된 []U의 underlying은 nil. U를 찾아가서 *int를 평가하고, int는 미리 선언된(predeclared) 타입이라 이미 완성 상태. 그러면 역순으로 *intU[]UT가 차례로 완성됨

  • 타입 **완전성(completeness)**이 핵심 속성: 모든 내부 필드가 채워지고, 참조하는 타입들도 모두 완성된 상태를 의미함. 완전해야 타입을 분해(deconstruct)해서 내부를 들여다보는 게 안전함

재귀 타입에서 복잡해지는 부분

  • type T []U; type U *T 같은 재귀 타입에서는 *T의 base 타입이 아직 완성 안 된 T를 가리키게 됨. 이때는 "나중에 T가 완성되면 괜찮아질 것"이라 가정하고 일단 포인터만 연결해둠. 루프 안의 타입들이 동시에 완성되면서 해결됨

  • 재귀 타입이 등장하면 "평가 결과가 항상 완전한 타입"이라는 편리한 속성이 깨짐. 그래서 map 키의 comparable 체크 같은 분해가 필요한 검사는 타입 체킹 마지막에 모든 타입이 완성된 후로 지연시킴

진짜 문제: 배열 크기와 불완전한 값

  • type T [unsafe.Sizeof(T{})]int 같은 경우가 진짜 골치아픔. 배열의 크기를 계산하려면 T의 크기를 알아야 하는데, T의 크기는 배열이 완성돼야 알 수 있음. T는 배열 완성을 기다리고, 배열은 T 완성을 기다리는 교착 상태

  • 이건 순환 정의(cyclic definition) 에러. type T T도 같은 류의 문제임

  • Go 1.26의 해법: 불완전한 값(incomplete value)이 생성되는 업스트림 지점(변환, 함수 호출, 타입 단언, 채널 수신, 맵 접근, 역참조 등)에서 결과 타입의 완전성을 즉시 체크. 불완전하면 순환 에러를 보고하고 invalid operand를 반환해서 불완전한 값이 다운스트림으로 퍼지는 걸 차단함

  • 예외적으로 type T [unsafe.Sizeof(new(T))]int유효함. *T 타입인 값의 크기는 T의 내부를 몰라도 알 수 있기 때문 (모든 포인터 크기는 동일)

중요

> 이 개선으로 기존에 발생하던 여러 컴파일러 패닉(#75918, #76383, #76384, #76478 등)이 해결됨. 기존의 복잡한 bespoke 순환 감지를 더 단순하고 체계적인 접근으로 교체한 것.

  • 저자의 결론: Go가 단순한 타입 시스템으로 유명하지만, 재귀 타입 + 크기가 있는 배열 타입이 만나면 생각보다 복잡한 문제가 숨어 있다는 거임. 프로그래머로서 당연하게 쓰는 기능 뒤에 있는 미묘한 복잡성을 들여다보는 재미있는 글

핵심 포인트

  • 타입 완전성(completeness)이 타입 분해(deconstruction)의 전제 조건
  • 재귀 타입에서는 루프 내 타입이 동시에 완성되는 트릭으로 해결
  • 배열 크기에 unsafe.Sizeof(T{}) 같은 순환 정의는 교착 상태 유발
  • 불완전한 값의 업스트림(변환, 함수 호출 등)에서 완전성 즉시 체크
  • 컴파일러 패닉 #75918, #76383 등 다수 해결

인사이트

Go의 단순한 타입 시스템 이면에 숨은 미묘한 복잡성을 보여주는 흥미로운 글. 재귀 타입 + 크기 있는 배열의 조합이 만드는 문제가 생각보다 깊음.

댓글

댓글

댓글을 불러오는 중...

backend

Redis 8.0 출시 — I/O 스레딩 갈아엎고 처리량 3배, 2.1M ops/sec 달성

Redis 8.0이 I/O 스레딩 모델을 완전히 재설계해서 16코어 기준 2.1M ops/sec를 달성함 (7.4 대비 3배). Hash field expiration, Vector search HNSW, Client-side caching v2, Redis Functions 2.0 async 실행 등 굵직한 기능이 추가되고, jemalloc 통합으로 메모리 fragmentation도 25% 줄어듦.

backend

Cloudflare Gen 13 서버: 캐시를 코어로 바꿔 성능 2배 달성한 이야기

Cloudflare가 AMD Turin 9965(192코어) 기반 Gen 13 서버를 배포함. 코어당 L3 캐시가 6배 줄어 레거시 NGINX 스택(FL1)으로는 레이턴시 50% 악화가 불가피했으나, Rust로 전면 재작성한 FL2로 전환해 Gen 12 대비 처리량 2배, 성능/와트 50% 개선을 달성함.

backend

칩셋 레이턴시를 측정해봤더니 — 쓸모는 없지만 재밌는 실험

Vulkan GPU 벤치마크로 여러 세대 마더보드 칩셋의 PCIe 레이턴시를 측정한 실험. CPU 직결 대비 칩셋 경유 시 수백 ns 레이턴시가 추가되며, 의외로 2012년 Skylake Z170이 가장 낮은 추가 레이턴시를 보임.

backend

ForgeKV — Rust로 만든 멀티코어 Redis 대체제

Rust로 만든 Redis 드롭인 대체제. 64-샤드 잠금 아키텍처로 멀티코어 스케일링 지원. 2코어 환경에서 Redis 7 대비 41% 빠른 SET 처리량(158K ops/s). 고동시성에서는 약점 있음. Source-available 라이선스.

backend

AI 바이브 코딩에 Go를 쓰는 이유 — Rust도 Python도 아닌

AI가 코드의 90%를 쓰는 바이브 코딩 시대에 Go가 최적의 언어라는 주장. Python은 안전장치가 없고, Rust는 언어 자체의 문제까지 인간에게 떠넘기지만, Go는 중요한 것만 잡고 비켜준다는 논리. 실제로 한 세션에 3도메인 풀스택 블로그를 7커밋으로 완성한 사례 제시.