1P by GN⁺ | ★ favorite | 댓글 1개
  • 이번 릴리스는 제네릭 메서드를 중심으로 구조체 리터럴과 타입 추론을 개선하고, 표준 라이브러리·런타임·도구 체인 전반에 실용적인 기능을 더함
  • 크기 특화 메모리 할당으로 80바이트 미만인 일부 할당 비용을 최대 30% 줄이며, 할당 집약형 프로그램에서는 약 1%의 성능 향상과 약 60KB의 바이너리 증가가 예상됨
  • encoding/json/v2가 정식 제공되고 기존 encoding/json도 내부적으로 v2를 사용하며, 표준 UUID 패키지와 FIPS 204 기반 ML-DSA 서명이 추가됨
  • goroutineleak 프로필, traceback의 pprof 레이블, 가상 시간용 synctest.Sleep, 메모리 내 HTTP 테스트 서버로 진단과 동시성 테스트가 쉬워짐
  • 타이머 채널의 비버퍼화, HTTP 응답 본문 자동 소진, //go:linkname 제한 강화는 기존 코드에 영향을 줄 수 있어 Go 1.27 전환 전 호환성 검사가 필요함

언어와 타입 시스템

  • Go 1.27의 핵심인 제네릭 메서드는 리시버와 독립적인 자체 타입 매개변수를 선언할 수 있음
    • 이전에는 최상위 함수만 제네릭일 수 있어 타입에 속하는 연산도 패키지 함수로 작성해야 했음
    • Box[T].Map[U]처럼 입력과 다른 요소 타입을 반환하는 연산을 메서드로 정의할 수 있음
    • 인터페이스는 여전히 타입 매개변수 메서드를 선언할 수 없으며, 제네릭 메서드로 인터페이스를 구현할 수도 없음
  • 구조체 리터럴 키에 최상위 필드명뿐 아니라 유효한 필드 선택자를 사용할 수 있음
    • 임베디드 구조체에서 승격된 필드를 User{ID: 7}처럼 직접 초기화할 수 있음
  • 함수 타입 추론이 변환과 복합 리터럴까지 확대됨
    • []func([]int) int{first, last}처럼 기대되는 함수 타입을 바탕으로 제네릭 함수의 타입 인자를 추론함
    • 이전에는 first[int], last[int]처럼 직접 인스턴스화해야 했음

런타임과 성능

  • 컴파일러가 크기 특화 할당 루틴을 호출해 80바이트 미만인 일부 메모리 할당 비용을 최대 30% 절감함
    • 실제 할당 집약형 프로그램의 전체 성능 향상은 약 1%로 예상됨
    • 코드 수정은 필요 없지만 작업 부하와 무관하게 바이너리가 약 60KB 커짐
    • GOEXPERIMENT=nosizespecializedmalloc으로 비활성화할 수 있으나 이 선택지는 Go 1.28에서 제거될 예정임
  • 기본 활성화되는 컴파일러 최적화 세 가지가 추가됨
    • 값에서 확실히 0 또는 1인 비트를 추적해 중복 연산을 제거하는 known bits 데이터 흐름 패스
    • 반복 중 바뀌지 않는 계산을 루프 밖으로 옮기는 루프 불변 코드 이동
    • 조건이 맞는 switchfallthrough까지 포함해 조회 테이블로 컴파일함
  • 실험적 GOEXPERIMENT=mapsplitgroup은 맵 그룹의 메모리 배치를 KVKVKVKV에서 KKKKVVVV로 바꾸며 기본값은 꺼져 있음
  • 링커는 타입 디스크립터와 itab을 전용 .go.type 섹션으로 옮기고 typelinksitablinks를 제거함
    • reflect.typelinks가 오프셋 대신 타입을 반환하므로 이 심볼에 //go:linkname으로 접근하는 라이브러리가 영향을 받을 수 있음

고루틴 진단과 프로파일링

  • go.mod가 Go 1.27 이상인 모듈은 traceback의 고루틴 헤더에 runtime/pprof 레이블을 표시함
    • pprof.Do로 붙인 {request: 42} 같은 문맥이 충돌 덤프, SIGQUIT, runtime.Stack 출력에 나타남
    • 민감한 레이블이 traceback에 노출되지 않도록 GODEBUG=tracebacklabels=0을 계속 사용할 수 있음
  • Go 1.26의 실험 기능이었던 고루틴 누수 탐지기가 정식 goroutineleak 프로필로 승격됨
    • GC를 실행해 채널이나 뮤텍스 등에서 영원히 진행할 수 없는 고루틴을 찾고 스택을 출력함
    • 서비스에서는 /debug/pprof/goroutineleak 엔드포인트로 수집할 수 있으며 GOEXPERIMENT가 필요 없음

암호화와 식별자

  • crypto/mldsa 패키지는 FIPS 204의 포스트 양자 서명 ML-DSA를 구현함
    • MLDSA44, MLDSA65, MLDSA87 세 매개변수 세트가 키·서명 크기와 보안 수준을 절충함
    • crypto/x509의 키·서명과 crypto/tls의 TLS 1.3 서명 방식에도 ML-DSA가 추가됨
  • 표준 라이브러리의 새 uuid 패키지는 RFC 9562 UUID를 암호학적으로 안전한 난수로 생성하고 파싱함
    • uuid.New()는 일반적인 용도에 맞는 방식을 선택하며 NewV4()는 순수 난수, NewV7()은 생성 시간순 UUID를 만듦
    • 난수 구성 UUID는 비교 가능해 ==를 직접 사용할 수 있음
    • 시간순으로 정렬되는 UUID v7은 데이터베이스 키에 적합함

JSON과 데이터 처리 API

  • encoding/json/v2와 저수준 encoding/json/jsontextGOEXPERIMENT=jsonv2 없이 제공됨
    • 기존 encoding/json v1도 내부적으로 v2 구현을 사용함
    • 일부 오류 메시지 문구를 제외하고 기존 동작을 유지하므로 마이그레이션은 필요 없음
    • 호환성 문제가 있으면 GOEXPERIMENT=nojsonv2로 원래 v1 구현을 복원할 수 있음
    • v2는 성능을 위해 맵 키를 기본 정렬하지 않으므로 안정적인 출력이 필요한 테스트에서는 json.Deterministic을 지정해야 함
  • strings.CutLastbytes.CutLast는 마지막 구분자를 기준으로 값을 나눔
    • 구분자가 없으면 전체 입력, 빈 후반부, false를 반환함
  • hash/maphash의 제네릭 Hasher[T]는 값의 해시와 동등성 전략을 함께 정의함
    • 동등한 값은 반드시 같은 해시를 가져야 함
    • 비교 가능한 타입용 ComparableHasher[T]가 제공되며 대소문자를 무시하는 문자열처럼 사용자 정의 전략도 구현할 수 있음
  • math/big.Int.Divide는 몫과 나머지를 함께 계산하면서 Trunc, Floor, Round, Ceil 중 명시적인 반올림 방식을 적용함
    • 반올림 방식에 따라 나머지도 달라져 금융·수치 코드에서 기존 QuoMod의 0 방향 절삭 한계를 보완함
  • math/rand/v2.(*Rand).N은 자체 *Rand 소스에서 임의의 정수 또는 duration 타입에 대한 범위 제한 난수를 생성함

이식 가능한 SIMD

  • 실험적 simd 패키지는 벡터 크기에 독립적인 SIMD API를 제공하며 GOEXPERIMENT=simd로 활성화함
    • 지원 환경에서는 실제 하드웨어 벡터 명령으로 컴파일되고, 그렇지 않으면 순수 Go 에뮬레이션으로 대체됨
    • Float32s 같은 타입의 레인 수는 고정되지 않고 머신별 하드웨어 폭을 따름
  • 표준 라이브러리는 이미 내부 simd/archsimd 인트린식을 사용함
    • Swiss Table 맵의 MemHash32, MemHash64, StrHash가 새 SIMD 기반으로 재구현됨

결정론적 테스트

  • testing/synctest.Sleep은 가상 시간을 진행하고 모든 고루틴이 안정될 때까지 기다리는 작업을 한 번에 수행함
    • synctest 버블 안에서는 실제 시간이 흐르지 않아 2초 대기도 즉시 완료됨
    • 기존 time.Sleepsynctest.Wait 조합의 반복 코드를 대체함
  • httptest.NewTestServer는 실제 TCP 포트 대신 메모리 내 가상 네트워크로 서버를 실행함
    • srv.Client()가 프로세스 내부 파이프로 핸들러에 직접 연결됨
    • t.Cleanup으로 자동 정리되므로 defer srv.Close()가 필요 없음
    • testing/synctest와 결합하면 HTTP 왕복도 가상 시간에서 빠르고 결정론적으로 테스트할 수 있음

네트워크와 호환성 변화

  • time.After, time.NewTimer, time.NewTicker 등이 반환하는 채널은 항상 동기식 비버퍼 채널이 됨
    • 과거 버퍼 동작을 복원하던 asynctimerchan GODEBUG 설정은 제거됨
  • HTTP/1에서 http.Response.Body.Close는 읽지 않은 본문을 보수적인 한도까지 읽어 연결 재사용을 도움
    • 큰 다운로드를 조기 종료하려고 Close에 의존했다면 Transport.DisableKeepAlives로 재사용을 비활성화해야 함
  • HTTP/2 서버는 RFC 9218 클라이언트 우선순위를 이해하고 높은 우선순위 스트림부터 처리함
    • Server.DisableClientPriority = true로 기존 라운드로빈 동작을 복원할 수 있음
  • Windows와 macOS의 crypto/x509.SystemCertPoolSSL_CERT_FILESSL_CERT_DIR을 지원함
    • 변수가 설정되면 디스크의 루트 인증서를 읽고 플랫폼 API 대신 Go 검증기를 사용함
    • GODEBUG=x509sslcertoverrideplatform=0으로 비활성화할 수 있음
  • 기존 12,226줄의 생성 파일 h2_bundle.go에 포함되던 HTTP/2 구현이 실제 net/http/internal/http2 패키지로 교체됨
  • net/http에는 외부 공개 전 단계의 플러그형 HTTP/3 훅이 추가됐고, 테스트 스위트 일부가 HTTP/3에서 실행됨
    • 호출 가능한 공개 API는 아직 없지만 향후 QUIC 기반 http.Transport를 위한 기반이 포함됨

도구 체인

  • go testgo.mod에 선언된 Go 버전보다 새로운 표준 라이브러리 심볼 사용을 찾는 stdversion vet 검사를 기본 실행함
  • go doc pkg@version으로 특정 모듈 버전의 문서를 조회할 수 있음
  • go doc -ex는 실행 가능한 예제를 나열하며, 예제 이름을 직접 지정하면 소스를 출력함
  • go fixatomictypes, embedlit, slicesbackward, unsafefuncs 현대화 분석기가 추가됨
    • waitgroupwaitgroupgo로 이름이 바뀌고 fmtappendf는 제거됨
  • Go 1.27 이상 모듈에서 go mod tidy는 흩어진 require 블록을 직접·간접 의존성용 두 블록으로 합치면서 주석을 보존함
  • go tool trace -http=:6060은 포트만 주어지면 localhost에만 바인딩하며, 외부 접근에는 주소를 명시해야 함
  • go 명령은 Bazaar(bzr) 버전 관리 시스템 지원을 제거함
  • compile, link, asm, cgo, cover, pack은 GCC 형식의 응답 파일(@file) 을 지원해 명령행 길이 제한을 피할 수 있음

Unicode와 보안 경계

  • 표준 라이브러리의 Unicode 데이터가 Unicode 15에서 Unicode 17로 갱신됨
    • Unicode 16에 추가된 문자도 심볼·출력 가능 문자로 올바르게 분류됨
  • os.Root.ReadDirReaddir을 이용해 루트 밖으로 탈출할 수 있던 문제가 수정됨
  • 승인되지 않은 //go:linkname 사용이 더 어려워짐
    • linknamestd 지시어는 표준 라이브러리만 가져올 수 있는 linkname을 표시함
    • 링커가 어셈블리 심볼에 대한 linkname 접근도 검사하므로 비공식 내부 심볼에 의존하는 코드는 조기 테스트가 필요함

댓글과 토론

Lobste.rs 의견들
  • 역대 최고의 릴리스일지도 모름. SIMD, uuid, jsonv2가 드디어 들어옴

    • UUID v5도 지원해야 완전히 이전할 수 있을 듯함
  • 다소 주제에서 벗어나지만, 모든 것을 문자열로 취급하고 인터페이스 메서드 사이의 계약과 연관성을 고려하지 않을 때 버그가 생기는 좋은 예임
    Equal은 대소문자를 무시하지만 Hash는 소문자로 변환한 값을 사용함. 그러나 EqualFoldToLower는 동일한 동등성 관계를 정의하지 않음
    https://github.com/golang/go/issues/52204
    "\u017F"(ſ, 긴 s)와 "s"EqualFold에서 같지만, 각각 ToLower한 결과는 다름. 따라서 "s"를 넣은 뒤 "ſ"를 넣으면 해시가 달라 별도 항목으로 삽입됨
    더 재미있는 점은 문서에서 바로 이 경우를 경고하고 있다는 것임
    https://github.com/golang/go/…

  • Anton이 멈춘 지점부터 다른 사람이 이어받아 반가움

    • 아쉽게도 적어도 일부, 특히 JSON v2 기본 사용 부분은 LLM으로 작성한 듯함. 문체는 마음에 들지 않지만 내용은 여전히 흥미로움
    • Anton이 중단한 것은 아닌 듯함. 1.26도 작성했으니 이번 글은 다른 사람이 먼저 공개한 것으로 보임
  • Go를 좋아하지만, 릴리스마다 이런 기능을 계속 덧붙이기에 Go 문법이 충분히 우아한지는 의문임
    func (b Box[T]) Map[U any](f func(T) U) Box[U] {

    • 주관적이지만 이 기능을 지원하는 다른 언어보다 나쁘다고 보지는 않음. Zig가 가장 비슷하고, Rust·TypeScript·C++20은 대체로 타입을 메서드 정의의 컨테이너로 삼아 여러 메서드에서 수신자 타입을 반복하는 비용을 줄임
      반면 Go와 Zig의 장황함은 수신자 정의를 찾으려고 다른 곳으로 스크롤할 필요가 없어서 좋음. 정의부보다 더 중요하다고 볼 수 있는 호출 지점에서는 모두 거의 똑같이 보이며 타입을 명시할 필요도 없음
  • 한동안 Go를 떠나 있었는데 jsonv2가 기대됨. 다만 기본값이 된다면 Go가 오랫동안 지켜 온 호환성 약속을 깨는 것 아닌지 궁금함

    • jsonv2는 별도 경로인 encoding/json/v2에서 가져와야 하므로 직접 선택해야 함. 기존 encoding/json은 내부적으로 v2를 사용하지만 종전 동작을 보장하며, jsonv2에는 v1의 예외적 동작을 재현하는 플래그도 마련돼 있어 호환성 약속을 깨지 않음
    • Go 1.26 프로젝트에서 v2의 jsontext 라이브러리를 사용 중인데 매우 만족스러움