Goの`zero`識別子追加プロポーザルと導入される仕様について

概要

zero 識別子が追加されることが決まったので該当プロポーザルの spec: add untyped builtin zero · Issue #61372 · golang/go を読んで気になったことや実際の導入予定のzero識別子の仕様についてまとめました。1

提案時の仕様

spec: add untyped builtin zero · Issue #61372 · golang/go

  • zero 識別子を追加する
  • zero はどの型に対しても代入可能な zero value を表す
    • (なので、ポインタ型と値型にも同じように代入可能で*x = zerox = zero が許容される)
  • 0, "", nil と比較できないような型Tの値であっても、zero と比較して zero value であるかを判定できる。Tがanyの場合も含む。

この zero 識別子を追加する提案はいきなり出てきたものではなく過去にあった以下のような同様のプロポーザルをまとめたものだそうです。

  • built-in function としてiszero()の追加
  • zero value を返す built-in function の zero(T)の追加
  • {}_ を untyped zero の値として使う提案

提案された zero 識別子があると嬉しいこと

  • 記述が簡潔にできる。例えば、...
    • generics での*new(T)zero
    • return time.Time{}, errreturn zero, err
  • generics でnon-camparableな型でもzeroとの比較が可能になる
    • 比較できない型で zero value と比較する処理は頻度は低いが cmp.Orには出てきていて、現在は Reflectを使うしか方法がない
    • 具体的には、サンプルコード のようなケースでコンパイルエラーになります。

提案を読んで気になったこと

  • 過去にあった提案として {}_untyped zero に採用する提案があったと書かれているけどなぜ採用してないのか

    0, "", nil が妥当な箇所では、zeroは妥当ではないので利用法を混乱することはないだろう

  • と提案では書かれているがどういう意味か? nilzero はどう使い分けたらいいのか?

  • zero があると記述が簡潔になるというけど、どの場面で生きるのか
    • genericsを書いていないので具体的なコードが思いつかな
    • コメント見ていると comparable とnon-comparableが出てきてるけどどう関係してるんだろう

このあたりのことを気にしながらコメントを読み進めました。

コメントであった議論で気になったものまとめ

識別子を追加するのではなく、既存の nil を拡張するのはどうか?

https://go.dev/play/p/fwaZAhmqXFb

var _ *int = nil
var _ *string = nil
var _ *a = nil
var _ map![image](string)int = nil
var _ []int = nil
// ↑ は許容される
var _ int = nil などは現在は許されない。これをできるようにするのではだめなのか?という議論

もし var _ int = nil をできるようにしたなら、x がポインタの時にポインタの初期化をしようとして、x = nil*x = nil を混同しやすくなってしまう。 また、これはPlan 9C言語のヘッダでも#define nil ((void*)0) のようにしたことで問題になっていたので避けたい。 とのことで却下されていました。2

genericsで簡潔に書けるようになることの具体例

代表ケースは、genericsなコードで初期値を返したり比較したい時に一度宣言して比較しないといけないのを書かなくて良くなること。 例として、cmp packageのIssueコメントを挙げられていた。 コメント内のコードで v T と Tの zero valueを比較するために一度、var zero T のようにして zero value で初期化された変数を一旦宣言しているが、この変数宣言が不要になる。

この提案でうまいなと思ったのは、var zero Tzero識別子を追加した後に残っていたとしてもGoでは宣言済みの識別子を上書きできるのでコンパイルエラーにならず挙動も元と変わらないことです。

また、コメント内のコードはTcomparable な型だけに縛られている。 これは、Goにおいて==がcomparableな型のみであり、Go言語のcomparableには3つの意味があるに書かれているように mapやslice、関数の値はnon-comparableで==での比較が行えないから。 とはいえ、non-camparableな型であっても nil との比較だけは可能です。 そしてmapやslice、関数の値のzero valuenil なので、上記のコメントのコードでもanyな型であってもコンパイルが通って欲しいとなる。

提案では zero は、 型Tがanyな時もzeroと比較してzero valueであるか比較可能としているので、上記のコンパイルが通って欲しいのに通らない気持ち悪さを解消できる。

追加予定の zero 識別子の仕様の簡単なまとめ

正確な表現は、仕様書変更差分 をご参照ください。 https://gospec-previewer.vercel.app/refs/ff6a26780f87f175c64b854ad3d2c663e9038bf4#Assignability Version of July 20, 2023 - Go Language Specification Previewer

  • zeronil の拡張ではない
    • 型Tの変数 x 次のケースに当てはまれば、zero が代入可能である
    • 型Tが 型パラメータではない かつ 型Tが構造体or配列の時
      • return time.Time{} のような箇所が return zero と書けて見やすいし型を変えた時も直さなくて良くなって嬉しい)
    • 型Tが 型パラメータである かつ 型パラメータTに 0, "", nil が代入できない時
      • つまり、T Any のような時には使えるが、 T int64 | float64 のような var x T = 0 ができる時はzeroの代入が許可されない

  1. プロポーザルを読むのは難しそうなイメージがありますが言語の仕様がどう作られるのかを知れるのが面白いし、落ち着いて翻訳ツールや具体的なサンプルコードを考えたり読んだりしていけば英語であっても十分読み進められる。
  2. nilは、辞書によるとzeroやnothingという意味もあるのでzeroとして使うのは間違いではないし、識別子を増やさないという点で利点がある。Nil Definition & Meaning | Dictionary.com