1P by GN⁺ | ★ favorite | 댓글 1개
  • for 루프 변수가 반복마다 새로 생성되도록 바뀌어 의도치 않은 공유 버그를 피하는 동작으로 변경, 정수에 대한 range도 가능해짐
  • 호환성은 Go 1 호환성 약속을 유지하며, 거의 모든 Go 프로그램이 이전처럼 컴파일·실행될 것으로 기대됨
  • 워크스페이스에서 go work vendor로 생성한 vendor 디렉터리를 사용할 수 있으며, 워크스페이스 vendor가 있으면 -mod=vendor가 기본값이 됨
  • GO111MODULE=off인 legacy GOPATH 모드에서 모듈 밖 go get 지원 중단, go buildgo test 같은 다른 빌드 명령은 legacy GOPATH 프로그램에서 계속 동작함
  • 런타임이 힙 객체 가까이에 타입 기반 GC 메타데이터를 두도록 바뀌어 Go 프로그램의 CPU 성능이 1–3% 개선되고, 다수 프로그램의 메모리 오버헤드가 약 1% 감소함
  • 일부 객체 주소 정렬이 기존 16바이트 이상에서 8바이트 정렬로 바뀔 수 있으며, 더 높은 정렬을 가정한 어셈블리 코드는 수정 필요, 임시 우회로 GOEXPERIMENT=noallocheaders 사용 가능
  • PGO 빌드가 더 많은 호출을 devirtualize할 수 있으며, 대표 Go 프로그램 집합에서 PGO 활성화 시 런타임 성능이 2–14% 개선됨
  • 표준 라이브러리에 첫 v2 패키지인 math/rand/v2 추가, Read 미승계, 무조건 랜덤 시드되는 전역 생성기, ChaCha8PCG 소스, N 제네릭 함수 제공
  • net/http.ServeMux 라우팅 패턴이 메서드와 와일드카드를 지원하며, {}가 포함된 패턴과 이스케이프 경로 처리에서 작은 하위 호환성 깨짐이 있고 GODEBUG=httpmuxgo121=1로 이전 동작 복원 가능
  • crypto/tls 서버의 기본 최소 버전이 미지정 시 TLS 1.2로 변경되고, pre-TLS 1.3 핸드셰이크에서 ECDHE 없는 cipher suite가 기본 제공되지 않음
  • 실행 트레이서가 전면 개편되어 대부분 플랫폼에서 OS 시계를 사용하고, 스트리밍 처리가 가능한 분할 trace, 전체 syscall duration, goroutine이 실행된 OS thread 정보 제공
  • trace 도구 웹 UI가 새 tracer 지원 작업의 일부로 갱신되고, Go 1.22 이상으로 빌드된 프로그램의 trace에서 thread-oriented view와 전체 syscall duration 표시 지원
  • vet이 Go 1.22 루프 변수 의미에 맞춰 loop closure 경고를 조정하고, 값 없는 append, defer 안의 비지연 time.Since, log/slog key/value 불일치 경고 추가
  • 부트스트랩에 Go 1.20의 최종 point release 이상 필요, Go 1.24는 Go 1.22의 최종 point release 이상을 요구할 것으로 예상됨
  • darwin/amd64에서 Go toolchain이 기본적으로 PIE를 생성하며, Go 1.22는 macOS 10.15 Catalina에서 실행되는 마지막 릴리스이고 Go 1.23은 macOS 11 Big Sur 이상 필요
  • go/typesAlias 타입과 Unalias 함수가 추가됐으며, gotypesalias=1에서는 클라이언트가 Alias 타입을 처리해야 함
  • slices.Delete, DeleteFunc, Compact, CompactFunc, Replace가 축소된 구간의 요소를 zero 처리하고, Inserti가 범위를 벗어나면 항상 panic 발생
  • Windows 디렉터리 읽기 함수들이 배치로 항목을 읽어 시스템 호출 수를 줄이며, 성능이 최대 30% 개선됨

댓글과 토론

Hacker News 의견들
  • 지난 3년은 주로 TypeScript를 쓰다가 최근 Go를 쓰기 시작했는데, 처음엔 배열 함수 부족이나 덜 유연한 타입 시스템 때문에 조금 걱정했음
    그런데 4~5명 규모 팀용 TypeScript 프로젝트를 다시 세팅해 보니, 린팅, 서버 라우팅 라이브러리 선택, 서버 선택, 코딩 표준, 기본 오류 처리, 중첩 try/catch 지옥을 피하려고 커스텀 오류나 Result 타입 강제, 테스트와 모킹, Prisma 설정 등에 시간이 너무 많이 듦
    TypeScript를 좋아하긴 하지만, 훌륭한 표준 라이브러리, 제대로 된 타입 안전성, 내장된 코딩 표준이 있는 언어를 쓰는 게 정말 편함. Go도 quirks는 있지만 꽤 괜찮고, 라우팅까지 표준 라이브러리로 들어온 건 신경 쓸 게 하나 더 줄어든 셈이라 좋음. 이제 슬라이스용 map/filter/find 함수만 기다리는 중

    • CTO 뉴스레터에서 최근 TypeScript와 Go 릴리스를 비교했고 Go 1.22를 예로 들었음
      TypeScript는 릴리스마다 파워 유저를 위해 점점 복잡해지는 반면, Go는 정수에 대한 range처럼 빠져 있던 것을 넣어 쓰기 더 단순하게 만듦
      게임 후속작이 설정과 메커니즘을 계속 늘리다가 신규 유저를 위해 리셋이 필요해지는 것과 비슷한데, 프로그래밍 언어는 그렇게 하기 어려우니 Go가 이를 의식하는 게 좋음
    • 비슷하게 느낌. Go에 머무르게 만든 건 놀라울 정도로 통일된 빌드 도구 체인이었고, go:embedgo:generate로 할 수 있는 일은 다른 프로젝트에서 볼 때마다 인상적임
      golang.org/x 패키지도 거의 모든 인터넷 RFC 관련 구현이 바로 쓸 수 있게 준비되어 있어 좋음
    • 슬라이스용 map/filter/find가 오기 전까지는 lodash에서 영감을 받은 lo 라이브러리를 쓸 수 있음
      Go 프로젝트에서 애용하는 만능 도구임: https://github.com/samber/lo
    • 기능이 엄청 많지 않아서 오히려 작은 결정들이 줄어들고, 직관과 반대로 꽤 생산적임
    • TypeScript를 쓰는 고객사에서 한동안 일했는데, 나중엔 “Tricks Driven Development”라고 부르게 됨
      뭔가 하려고 문서를 읽으면, 곧 누군가 문서에는 없는 쓸 수 있는 트릭을 알려주는 식이 반복됐음
  • 공식 릴리스 노트가 조금 딱딱하다면, 인터랙티브 버전을 만들어 뒀음: https://antonz.org/go-1-22

    • for 루프 변수 공유 문제가 처음엔 바로 이해되지 않았는데, 직접 실행해서 예상 밖 결과를 보니 도움이 됨
    • CompactReplace 예제가 헷갈리는데, 이게 맞는 건지 모르겠음
  • Go를 9년 넘게 썼고, 최근 4년은 Flutter 때문에 Dart를 많이 썼는데 두 언어는 복잡성에 대한 태도가 정반대라고 봄. Dart는 가능한 모든 기능을 추가하려 하고, Go는 반대임
    첫째, Dart에서는 클래스 안에서 값을 초기화하는 방법이 너무 많아 시간을 많이 쓰게 됨. final/const/static/late 접두어에 따라 생성자, 팩토리, Flutter StatefulWidgetinitState() 등 초기화 위치가 달라지고, 리팩터링하면 초기화를 크게 손봐야 함. 함수가 변수처럼 보이는 getter도 새 코드베이스에서는 혼란을 더함
    둘째, Dart에는 맵, 스트림 등 한 줄에 몰아넣기 좋은 기능이 많아서, 이해하기 어려운 한 줄짜리 코드를 빠르게 만들고 싶어짐. 하지만 디버깅하거나 주니어 개발자가 firstWhereOrNull 같은 해킹과 orElse: () => null 같은 선택 매개변수를 만나면 매우 혼란스러워함. 난해한 null 안전성이나 타입 오류가 멈춰 세울 때도 많고, 이를 단순한 Go식 for 루프로 바꾸면 정말 안도감이 듦

    • Dart 팁으로, UI에 직접 관련 없는 일에는 initState를 쓰지 않는 게 좋음. 예를 들어 텍스트 필드 힌트를 설정하는 정도가 아니라면 피하는 편임
      대부분의 Flutter 페이지는 할 일이 아주 적거나, 부모가 MyPageController 객체를 만들고 원하는 방식으로 초기화한 뒤 자식 페이지가 그 컨트롤러에 따라 동작하게 함. 예를 들어 목록 페이지에서 사용자가 항목을 편집하려 할 때 컨트롤러를 만들고 항목을 넣어 자식 페이지로 보내면, 돌아온 뒤 부모가 컨트롤러의 항목이나 변수·콜백을 보고 UI 변경을 결정할 수 있음
      이렇게 하면 InheritedWidget 같은 것을 건드리지 않고도 위젯 간 상호작용을 세밀하게 제어할 수 있음. 물론 상태 관리 라이브러리와 함께 쓰는 게 좋지만, 실제로는 안 쓰는 경우도 많음
    • Go의 단순함이 가장 큰 장점이라는 데 동의함. range도 마찬가지로, 결국 타이핑하는 문자 수는 forEach(f) 같은 컴프리헨션과 비슷해도 세 줄로 퍼져 있어서 더 길어 보일 뿐이고, 직접 풀어 써 보면 별일 아님
      다만 초기화와 관련해서는 Go에 구조체 필드를 특정 값으로 초기화하는 관용적 방법이 있었으면 함. 생성자가 없는 건 괜찮지만, 가끔 bool이 기본적으로 true로 초기화되길 바랄 때가 있음
    • initStateStatefulWidget의 오버라이드이고 다른 프레임워크의 생명주기 이벤트와 다르지 않으며, Flutter 쪽 개념이라 생성자와 헷갈릴 성질은 아님
      일반 생성자와 팩토리 중 무엇을 써야 할지 헷갈릴 수는 있겠지만, initState 때문에 Go가 우월하다고 보기는 어려움
    • 비싼 전문가들이 서로 간결하게 소통할 수 있는 능력을 최적화해야 함. 초보자 손에서 강력한 문법 도구를 빼앗는 건 그들이 전문성을 키우는 걸 막을 뿐임
  • io.CopyTCPConn에서 UnixConn으로 복사할 때 가능하면 Linux의 splice(2) 시스템 호출을 쓰고, 새 메서드 TCPConn.WriteTo를 사용하게 됨
    인터페이스 업그레이드로 또 한 번 투명하게 무복사 I/O가 늘어난 셈임. io 패키지에서 이 패턴으로 얻어내는 효과가 정말 큼

    • Go 표준 라이브러리에는 이런 식의 조정이 정말 많음. 꼭 나쁜 건 아니지만, 이런 특수 처리는 확장 가능하지 않아서 비표준 라이브러리와의 통합은 덜 만족스러워짐
      그래도 기대 범위 안에 있긴 함
  • 향상된 라우팅 패턴은 Gorilla Mux의 미래가 불안정한 지금 좋은 개선임
    다만 이 변경은 작은 방식으로 하위 호환성을 깨는데, {}가 있는 패턴 동작이 달라지는 명확한 부분도 있고 이스케이프된 경로 처리처럼 덜 명확한 부분도 있음. GODEBUGhttpmuxgo121 필드로 제어하고, httpmuxgo121=1을 설정하면 이전 동작으로 되돌릴 수 있음
    그런데 “Go 1 명세로 작성된 프로그램은 그 명세의 수명 동안 변경 없이 계속 컴파일되고 올바르게 실행된다”는 Go 1 호환성 약속과 충돌하지 않는 이유가 궁금함

    • Go 1 호환성 약속을 우회하는 방식은 go.modgo 지시문 값에 이런 하위 비호환 변경을 걸어 두는 것임
      1.22 이상이면 새 라이브러리 동작을 받고, 아니면 예전 동작을 받음. 실제로 얼마나 잘 작동할지는 지켜봐야 함
    • 보통 Go 팀은 뭔가를 깨기 전에 GitHub와 오픈소스 프로그램을 긁어 사람들이 어떻게 쓰는지 확인함. 아마 HTTP 핸들러 경로에서 {}를 쓰는 경우가 거의 없다고 봤을 것 같음
      새 동작을 끌 수 있는 방법도 제공하니 코드 변경을 강제하진 않지만, 새 환경 변수를 설정해야 하긴 함
      이번 릴리스의 for 루프 의미 변경도 비슷한 예로, 사실상 깨지는 변경임. 그래도 모든 Go 프로그램은 계속 컴파일되고 실행되며, 일부 동작이 조금 달라질 뿐임. Go가 실용적인 접근을 택했고, 그게 성공 이유 중 하나였다고 봄
    • 이건 Go에서 드문 실수라고 봄. go.mod를 이용해 버전별로 깨뜨릴 거라면 적어도 더 나은 방식으로 깨뜨려야 함
      go.mod를 1.22로 바꿨을 때 미묘한 런타임 이슈를 찾아내는 것보다, 차라리 코드가 컴파일 실패하는 편이 훨씬 낫겠음
    • 그러지 않으면 짧고 강한 광고 문구를 잃게 됨. 작든 아니든 호환되지 않는 변경
  • Go 1.22에서는 루프의 각 반복이 새 변수를 만들게 됨
    이 내용은 이전에도 여기서 논의됐음: https://news.ycombinator.com/item?id=33160236 - Go: Redefining For Loop Variable Semantics (2022)

  • 구식일 수도 있지만 함수에 대한 range 추가는 마음에 들지 않음. 언어에 추가되는 복잡성을 정당화할 만큼 편의성이 크지 않고, 함수형 스타일이 Go의 명시적이고 명령형이며 장황하지만 기능이 적은 스타일과 어긋나 보임. 그게 Go의 큰 장점 중 하나였다고 봄
    같은 이유로 정수에 대한 range도 잘못된 방향이라고 생각함. Go의 얇은 기능 집합과 낮은 인지 부하는 늘 주요 강점이자 차별점이었음

    • 현재 이 기능은 선택적·실험적이며 명시적으로 켜야 함. 표준으로 켜게 된다면, 흔히 쓰이지 않을 때 개발자가 쓰지 않도록 유도하는 끄기 메커니즘이 있었으면 함
      소프트웨어 개발에서 큰 문제 중 하나는 개발자가 복잡성을 추가하거나 실력을 과시하려는 걸 막는 일임. 특히 경력 초반 개발자는 문제를 직설적이지만 덜 우아하고 장황하게 풀고 넘어가기보다, 해법을 과도하게 복잡하게 만드는 경향이 있음
    • 이 기능은 아직 이른 느낌임. 이상적으로는 람다와 제네릭 매개변수 팩이 도입된 뒤 들어와야 함
      현재 Go의 제네릭 지원은 이를 받쳐 줄 만큼 정교하지 않아서 이상하게 강제된 제한이 생김
    • 나도 애매하게 봄. 인라인 함수를 넘기는 것과 비교해 코드 줄 수가 줄지도 않음
  • sql.Null[T] 추가는 좋음. 새 프로젝트에서는 아마 쓰기 시작할 듯함
    현재 작업물에서는 API가 아주 비슷한 sqlboiler의 null [0]에 의존하고 있음. sql.Null처럼 동작하지만, 값이 명시적으로 설정된 적 있는지 알려주는 IsSet() bool 메서드가 추가로 있어 “의도적 null”과 “초기화되지 않아서 null”을 구분하는 데 도움을 줌. 좋아 보이지만 실제로는 한 번도 써 본 적 없음

    • 언제 null이 의도적인지 알아야 하는지 궁금함. 데이터베이스에는 그에 대응하는 타입이 없지 않나
  • 표준 라이브러리 mux를 다시 살펴보고 chi 제거도 가능할지 기대 중임. 이런 기능이 표준 라이브러리로 들어오는 게 정말 좋음

    • 똑같은 말을 하러 왔음. chi는 몇몇 프로젝트에서 정말 훌륭했고, 사실 존재를 잊을 정도로 자연스러웠음
      이런 기능이 표준 라이브러리로 들어가면 항상 유지보수되고, 많은 Go 프로그램에서 같은 접근이 쓰이도록 보장됨
  • Go를 프로덕션에서 쓰는 경우 최신 버전으로 빨리 갈아탈 수 있는지, 아니면 오래된 릴리스에 묶이는지 궁금함
    공개된 프로젝트를 보면 비교적 새로운 기능을 피하는 경우가 많아 보임. 제네릭을 쓰지 않더라도 예전의 interface{} 대신 Go 1.18에서 나온 any를 쓰고 싶은데, 후자가 더 “관용적”이라는 말도 들어 봄

    • Square에서는 보통 Go 모노레포의 기본 버전을 올리기 전에 첫 번째 포인트 릴리스를 기다림. 개별 애플리케이션 소유자는 더 빨리 올려도 됨
      이번에는 약간 꼬여서, 우리가 기본값을 1.21로 올리기 전에 Go 팀이 1.22를 먼저 냈음. 이제 기다리는 건 거의 관례에 가깝고, Go 릴리스가 뭔가를 깨는 일은 드묾
    • 새 기능은 잘 맞는 문제가 보일 때까지 실험을 기다리는 편이지만, 컴파일러 버전은 릴리스 직후 거의 바로 올림
      예를 들어 제네릭은 실제로 쓰기까지 6개월 정도 기다리며 유용성, 개발자 경험, 빌드·테스트 속도 영향이 크지 않은지 확인했음
    • 직장에서는 운영 중인 컨테이너, 즉 SaaS/API 서비스는 개발자들이 최신 빌드로 자유롭게 올릴 수 있음
      하지만 Go로 최종 사용자 앱도 제공하는데, 이쪽은 오래된 클라이언트 OS와의 호환성 때문에 1.20에 머물러야 함. 1.21에서 상당한 컷이 있었기 때문에 몇 년은 1.20에 머물 가능성이 큼
    • CI가 Go의 latest 컨테이너로 빌드와 테스트를 하고, 테스트 스위트가 꽤 탄탄해서 사실상 자동 업데이트됨. 뭔가 안 맞으면 파이프라인이 깨지고, 그때 마지막으로 동작한 버전에 고정할지 아니면 깨지는 변경을 고칠 때까지 업데이트를 미룰지 결정함
      몇 년에 한 번 정도는 새 버전과 서드파티 패키지 사이에서 미묘한 엣지 케이스 버그가 드러나기도 하지만, 그런 버그는 정의상 테스트 스위트를 피한 것이므로 수동 업그레이드였어도 잡지 못했을 가능성이 큼
    • 바로 갈아탐
      오래된 Go 컴파일러 지원이 걱정된다면, 예전 버전용 빌드 조건을 두고 빠진 것을 정의할 수 있음. 예를 들면 // +build !go1.7 아래에 type any = interface{}를 둘 수 있음