1 ポイント 投稿者 GN⁺ 2024-02-08 | 1件のコメント | WhatsAppで共有
  • for ループ変数が反復ごとに新しく生成されるように変更され、意図しない共有バグを避ける挙動になり、整数に対する range も可能になった
  • 互換性は Go 1 の互換性の約束を維持しており、ほぼすべての Go プログラムがこれまでどおりコンパイル・実行できると見込まれる
  • ワークスペースで go work vendor によって生成した vendor ディレクトリを使用でき、ワークスペースに vendor がある場合は -mod=vendor がデフォルトになる
  • GO111MODULE=off のレガシー GOPATH モードで、モジュール外の go get のサポートを終了。go buildgo test などの他のビルドコマンドは、レガシー 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 ツールの Web UI は新しい tracer サポート作業の一環として更新され、Go 1.22 以降でビルドされたプログラムの trace で thread-oriented view と syscall 全体の duration 表示をサポートする
  • vet は Go 1.22 のループ変数の意味に合わせて loop closure 警告を調整し、値のない appenddefer 内の遅延されない time.Sincelog/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.DeleteDeleteFuncCompactCompactFuncReplace は縮小された区間の要素をゼロ化し、Inserti が範囲外なら常に panic を発生させる
  • Windows のディレクトリ読み取り関数は項目をバッチで読み込むようになり、システムコール数を減らして性能が最大 30% 改善する

1件のコメント

 
GN⁺ 2024-02-08
Hacker News の意見
  • この3年ほどは主に TypeScript を使っていて、最近 Go を使い始めたのですが、最初は配列関数の不足や、あまり柔軟ではない型システムが少し心配でした
    ところが、4〜5人規模のチーム向けに TypeScript プロジェクトを改めてセットアップしてみると、リンティング、サーバールーティングライブラリの選定、サーバーの選定、コーディング標準、基本的なエラー処理、ネストした try/catch 地獄を避けるためのカスタムエラーや Result 型の強制、テストとモック、Prisma 設定などにあまりにも時間がかかりました
    TypeScript は好きですが、優れた標準ライブラリ、きちんとした型安全性、組み込みのコーディング標準がある言語を使うのは本当に楽です。Go にも癖はありますがかなり良く、ルーティングまで標準ライブラリに入ったのは、気にすべきことがまた一つ減ったという意味でありがたいです。あとはスライス用の 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 を多く使ってきましたが、この2つの言語は複雑さに対する姿勢が正反対だと思います。Dart は可能な機能をすべて追加しようとし、Go はその逆です
    第一に、Dart ではクラス内で値を初期化する方法が多すぎて、かなり時間を使うことになります。final/const/static/late という接頭辞によって、コンストラクタ、ファクトリ、Flutter の StatefulWidgetinitState() など初期化場所が変わり、リファクタリングすると初期化を大きく手直ししなければなりません。関数が変数のように見える getter も、新しいコードベースでは混乱を増やします
    第二に、Dart にはマップやストリームなど、1行に詰め込みやすい機能が多いため、理解しにくいワンライナーをすぐ作りたくなります。しかし、デバッグ時やジュニア開発者が firstWhereOrNull のようなハックや orElse: () => null のようなオプション引数に出会うと、非常に混乱します。難解な null safety や型エラーで作業が止まることも多く、それを単純な Go 風の for ループに置き換えると本当にほっとします

    • Dart のコツとして、UI に直接関係しないことには initState を使わないのが良いです。たとえばテキストフィールドのヒントを設定する程度でなければ、避けるようにしています
      ほとんどの Flutter ページはやることがごく少ないか、親が MyPageController オブジェクトを作って望む形で初期化し、子ページがそのコントローラに従って動作するようにします。たとえば一覧ページでユーザーが項目を編集しようとしたときに、コントローラを作って項目を入れて子ページへ渡せば、戻ってきた後に親がコントローラの項目や変数・コールバックを見て UI 変更を決められます
      こうすると、InheritedWidget のようなものに触れなくても、ウィジェット間の相互作用を細かく制御できます。もちろん状態管理ライブラリと一緒に使うのが良いですが、実際には使わないことも多いです
    • Go のシンプルさが最大の長所だという点には同意します。range も同様で、結局タイプする文字数は forEach(f) のようなコンプリヘンションと似ていても、3行に広がっているので長く見えるだけで、実際に展開して書いてみれば大したことではありません
      ただし初期化に関しては、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{} を置ける。