概要
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 = zero
とx = 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{}, err
→return zero, err
- generics での
- generics でnon-camparableな型でも
zero
との比較が可能になる
提案を読んで気になったこと
過去にあった提案として
{}
や_
をuntyped zero
に採用する提案があったと書かれているけどなぜ採用してないのか0
,""
,nil
が妥当な箇所では、zero
は妥当ではないので利用法を混乱することはないだろうと提案では書かれているがどういう意味か?
nil
とzero
はどう使い分けたらいいのか?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 9のC言語のヘッダでも#define nil ((void*)0)
のようにしたことで問題になっていたので避けたい。
とのことで却下されていました。2
genericsで簡潔に書けるようになることの具体例
代表ケースは、genericsなコードで初期値を返したり比較したい時に一度宣言して比較しないといけないのを書かなくて良くなること。
例として、cmp packageのIssueコメントを挙げられていた。
コメント内のコードで v T
と Tの zero valueを比較するために一度、var zero T
のようにして zero value で初期化された変数を一旦宣言しているが、この変数宣言が不要になる。
この提案でうまいなと思ったのは、var zero T
がzero
識別子を追加した後に残っていたとしてもGoでは宣言済みの識別子を上書きできるのでコンパイルエラーにならず挙動も元と変わらないことです。
また、コメント内のコードはT
が comparable
な型だけに縛られている。
これは、Goにおいて==がcomparableな型のみであり、Go言語のcomparableには3つの意味があるに書かれているように mapやslice、関数の値はnon-comparableで==での比較が行えないから。
とはいえ、non-camparableな型であっても nil
との比較だけは可能です。
そしてmapやslice、関数の値のzero valueはnil
なので、上記のコメントのコードでも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
zero
はnil
の拡張ではない- 型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
の代入が許可されない
- つまり、
- 型Tの変数
- プロポーザルを読むのは難しそうなイメージがありますが言語の仕様がどう作られるのかを知れるのが面白いし、落ち着いて翻訳ツールや具体的なサンプルコードを考えたり読んだりしていけば英語であっても十分読み進められる。↩
- nilは、辞書によるとzeroやnothingという意味もあるのでzeroとして使うのは間違いではないし、識別子を増やさないという点で利点がある。Nil Definition & Meaning | Dictionary.com↩