Go Issue #9097 全コメント翻訳 ― &T(v)構文の提案と議論の全記録
golang/go#9097 は、2014年11月に提出された「&T(v) 構文を追加して、型Tの変数を確保し、値vで初期化し、そのアドレスを返す」という提案だ。2023年8月にクローズされるまでの約9年間に40件のコメントが寄せられた。
この記事では、本文と全コメントを漏れなく日本語に翻訳する。
Issue本文
著者: chai2010 | 投稿日: 2014-11-13
タイトル: proposal: spec: add &T(v) to allocate variable of type T, set to v, and return address
- new関数の改善 (by Albert Liu @ jpush)
func new(Type, value ...Type) *Type
&Type(value)構文のサポート例:
px := new(int, 9527) px := &int(9527)関連する議論: https://groups.google.com/d/msg/golang-nuts/I_nxdFuwAmE/jNObXNDy5bEJ
ラベル: LanguageChange, v2, Proposal, NeedsDecision, FrozenDueToAge
コメント 1
著者: cznic | 投稿日: 2014-11-13
- 提案されたnewのシグネチャにある
...は何のためにあるのか? newはただ1つの値へのポインタを返すものだ。&T{}と&T()の両方が同じことをするのは、控えめに言っても驚きだろう。- 構造体のアロケーションはよく行われるが、構造体以外の型のアロケーションはそうではない。
コメント 2
著者: chai2010 | 投稿日: 2014-11-13
#1
- valueはオプショナルなので、
...型が必要:px := new(int) px := new(int, 123) px := new([]int, 1, 2, 3) px := new([]int, x...)
&T{}は&int{}をサポートしていない- 議論のリンクを参照してほしい
コメント 3
著者: cznic | 投稿日: 2014-11-13
「
px := new([]int, 1, 2, 3)」ああ、スライスまでサポートするつもりなのか? しかしスライスリテラルとは異なる構文(
{[key:] value, ...})を使って? しかもlenとcapを設定する方法がない? mapはどう扱うつもりだ?pm := new(map[t]u, 1, 2, 3)? どれがキーでどれが値だ? それともmap型は例外として'Type'に含まれないのか? 等々。これらすべてが、この提案がいかに悪いアイデアかを示していると思う。
コメント 4
著者: chai2010 | 投稿日: 2014-11-13
mapの場合:
pmap := new(map[string]int, map[string]int{ "A": 1, "B": 2, })mapスライスの場合:
pmaps := new([]map[string]int, map[string]int{ "A": 1, "B": 2, }, map[string]int{ "A": 1, "B": 2, }, )
コメント 5
著者: cznic | 投稿日: 2014-11-13
つまりスライスには値のリストを使い(#2):
px := new([]int, 1, 2, 3)しかしmap型にはcomposite literalを使う(#4):
pmap := new(map[string]int, map[string]int{ "A": 1, "B": 2, })どちらが原則でどちらが例外なのか? スライスの場合も類推的にこう書くべきではないのか:
px := new([]int, {1, 2, 3}) // ?既存のkey: val構文もサポートするのか:
px := new([]int, {1, 42: 2, 3})つまり、「なぜ
...なのか」に戻ってくる。もし提案が受け入れられるとしたら(そうならないことを願うが)、こうあるべきだと思う:
new(T, optExpr) // 1はリテラルであり、{1, 2, 3}も同様ここでoptExprはオプショナルで、以下と同様だ:
make(T, optExpr1, optExpr2)ちなみに、忘れないでほしい ― Goの最大の長所は「機能が少ない」ことだ。
コメント 6
著者: ianlancetaylor | 投稿日: 2014-11-13
ラベル変更: repo-main, release-none, languagechange, go2 を追加。
コメント 7
著者: chai2010 | 投稿日: 2014-11-14
#5 すみません、間違えていました。私が望んでいるのはこの2つの
newの形だけです:func new(Type) *Type func new(Type, value Type) *Typeこちらの
newの形は含みません:func new([]Type, values ...Type) *[]Typeなぜなら、以下のような紛らわしいコードを引き起こすからです:
px := new([]int, []int{1}) px := new([]int, 1) // new([]int, 1, 2, 3) のように見えるいくつかの例:
px := new(int) px := new(int, 123) px := new([]int) px := new([]int, []int{1, 2, 3}) px := new(map[string]int) px := new(map[string]int, map[string]int{ "A": 1, "B": 2, "C": 3, })
コメント 8
著者: mikespook | 投稿日: 2014-11-14
このissueに関連するはずの提案があります。レビューしてコメントをいただけると嬉しいです。 https://docs.google.com/document/d/111YaXFZeJbJ9DhOF69CvvFV49YTkUKpIRKiS42woMak/edit?usp=sharing
コメント 9
著者: rsc (Russ Cox) | 投稿日: 2017-06-16
#19966 も参照。
コメント 10
著者: ianlancetaylor | 投稿日: 2018-01-03
new(int, 5)と&int(5)の両方が必要な理由がわからない。確かに今日、Tがcomposite型であれば、new(T)と&T{}の両方が許されている。両方が許されているということは、本質的にcomposite型Tに対してnew(T)と書く人はほぼいないということだ。もし&int(5)を許すなら、new(int, 5)と書く人もいなくなるだろう。だから、もし&int(5)を採用するなら、newを完全に削除することを検討すべきだ。この種のことに関しては、型
[]interface{}を考えるのが面白い。ここで提案されている構文では、&[]interface{}{nil}は値がnilの要素を1つ持つスライスを返し、&[]interface{}(nil)はnil型[]interface{}のnilスライスを返す。それ自体が、ここで()を好み、{}をcomposite型に予約する理由になる。私が思うに、ここでの提案は以下のようにすべきだ: 任意の型T、Tに代入可能な任意の値vに対して、式
&T(v)を言語に追加する。この式は型Tの新しい変数を確保し、値vに設定し、そのアドレスを返す。
コメント 11
著者: alercah | 投稿日: 2018-02-13
いいと思う。
コメント 12
著者: tv42 | 投稿日: 2018-02-21
もし
&T(v)が入るなら、func foo() Tが&T(foo())だけでなく&foo()も許すようにして、*Tを得られるようにすべきではないか。
コメント 13
著者: ianlancetaylor | 投稿日: 2018-02-21
@tv42 正しく理解しているなら、それはこの提案ではなく、#22647 だ。
コメント 14
著者: tv42 | 投稿日: 2018-02-21
@ianlancetaylor そのissueには
&foo()が含まれているようだ、確かに。ここに来たのは主に&T(v)と&foo(v)の構文の類似性のためで、&"bar"はもう少し突飛だ。
コメント 15
著者: benhoyt | 投稿日: 2018-03-28
これは良い提案だと思う(似たものの「経験報告」は#22647を参照)が、私はよりシンプルな
&"foo"や&1234の構文に一票だ。私には&T(v)構文よりも明白に思える。&T(v)は型変換か関数呼び出しのように見える。
&"foo"スタイルの構文は既存の&T{...}構文の自然な拡張のようにも思える: 何かを構築し、そのアドレスを取る。そして私の提案は、それが構造体(現在のように)であろうとintやstringやその他であろうと関係ないということだ。この構文は私がGoを学んでいるときに試したものだ。式に
&を付ければアドレスを取れて、コンパイラがそれを解決してくれると単純に思い込んでいた(Goはヒープかスタックかを「コンパイラに任せる」ことを重視している)。私だけではない:&T{...}の前例があるため、他の人もこれが動くと期待している。1つ目、2つ目、3つ目、4つ目を参照。よりシンプルな構文は式にも使える。
&time.Now()のような(単一値の)関数呼び出しや、&(x + 1234)のようなより一般的な式にも ― 後者は演算子の優先順位のために括弧が必要になるだけだ。とはいえ、そのような一般的な式はまれで、実際には定数や関数の戻り値のアドレスを取ることがほとんどだろう。
コメント 16
著者: ianlancetaylor | 投稿日: 2018-03-28
&1234はおそらく型*intを持つだろう。しかし時にはint64が必要なこともある。だから&1234では不十分で、「型int64の変数を作成し、1234に設定し、アドレスを返す」方法が必要だ。提案されている&T(v)構文なら&int64(1234)が可能だ。だから&T(v)のようなものはいずれにせよ必要だと思う。もし任意の式
vに対して&vを許したいなら、型変換を使って&int64(v)ができる。しかし任意の式に対する
&vにはいくつかの困難がある。論理的にはアドレスのアドレスを取ることが可能であるべきで、&&vとなる。しかしこれは&&が異なる意味を持つ演算子であるため動作しない。さらに重要なのは、
vが変数の場合、&varは変数でない式vに対する&vとはかなり異なるということだ。&varは唯一の変数varのアドレスを取る。ループ内で呼び出された場合、実行のたびに同じ値に解決される。変数でないvに対する&vは毎回新しいインスタンスを確保し、そのためループ内では実行のたびに異なる値に解決される。これは混乱を招きそうなかなり微妙な違いだ。上で
&"foo"は&T{...}の拡張だと言っているが、そうかどうかわからない。&T{...}は型が常に必要な特殊ケースであり、さらに重要なのは、毎回新しい値を確保することが明示的に定義されているということだ。
コメント 17
著者: benhoyt | 投稿日: 2018-03-28
ありがとう ― もっともだ。
&T(v)がそれらの微妙な問題を解決するという点には同意する。ただ、&&vの問題が本当に問題だとは思わない。非常にまれだし、もし本当に必要なら括弧を使って&(&v)とすればいい。それでも、
&T(v)アプローチだと、私の元々のユースケースである&time.Now()は非常に不格好になる:&time.Time(time.Now())。&varが毎回同じ値を返し、&exprがそうでないことは問題なのか?&varと&T{}ですでにその区別はあるのではないか?
コメント 18
著者: ianlancetaylor | 投稿日: 2018-03-28
確かに
&varと&T{}は異なる振る舞いをする。これは明確にドキュメント化されていて、見た目も異なる。(実際、アドレス付きcomposite literalの構文を(*T){}に変更すべきだという意見が一時期あった。そのほうが論理的だが、結局&T{}のままにした。)&varと&1は見た目がずっと似ているので、それらの振る舞いがかなり異なるという事実をより意識する必要がある。
&time.Time(time.Now())が不格好だというのは同意する。何も変更しないほうがいい理由かもしれない。これはすべて構文糖にすぎない。有用でなければならないし、明確でなければならない。
コメント 19
著者: creker | 投稿日: 2018-03-28
&1では不十分だというのはもっともだ。特定の型であることが必要で、Goでは数値定数はuntypedだからだ。しかしコンパイラに文脈から意味を推論するもっと多くの自由を与えてはどうか?
&"foo"-*string&time.Now()-*Time&1- 曖昧。コンパイラがエラーを出し、&int64(1)などを使う必要がある。しかしこの場合でもコンパイラは文脈を使って正確な型を決定できる。interface{}引数を持つ関数に渡すか、:=で変数を作成する場合は&T(v)構文を使う必要がある。適切に実装するのに十分な文脈があるように思える。コードを見るだけで簡単にどちらかわかる。魔法的なことも驚きもない。
コメント 20
著者: benhoyt | 投稿日: 2018-03-29
@creker Goでは数値定数はuntypedかもしれないが、整数を変数に代入するとき、型は常に
intだ。myInt := 1234のように。だから&1234は曖昧さなく&int(1234)を意味するのが明白だと思う。
コメント 21
著者: bcmills | 投稿日: 2018-03-29
C++の「uniform initialization」は嫌いだが、ここでは実際に良い例になるかもしれない。
&{x}(&の後に型がある場合もない場合も)を、匿名変数のアドレスを取る一般的な省略記法として許すことができる。通常の変数や式のアドレスを取ることとは視覚的に区別でき、構造体リテラルのアドレスを取ることとは視覚的に似ている。例:
px := &{1234} // px := new(int); *px = 1234 px64 := &int64{1234} // px64 = new(int64); *px64 = 1234 pt := &{time.Now()} // pt := new(time.Time); *pt = time.Now() pfoo := &{"foo"} // 以下同様#21496 と組み合わせると、構造体リテラルの唯一の特別な点は波括弧を重複させないことだけになる:
ps := &SomeStructType{"foo", "bar"} // ps := new(SomeStructType); *ps = {"foo", "bar"}
コメント 22
著者: bcmills | 投稿日: 2018-03-29
皮肉なことに、ちょうど今日
&(*x)についてのgolang-nutsスレッドがあった。これはIanの「アドレスのコピー」構文が「任意の式のアドレス」と視覚的に区別される必要があるという議論を裏付けていると思う。
コメント 23
著者: cznic | 投稿日: 2018-03-31
この提案には反対だが、もし受け入れられるとしたら、
&T{...}のTをintやstringなどの単純な型にも許すように制限を緩和するだけにしたい。これはuntyped constantの結果型の問題も解決する。&int32{42}や&int64{42}、あるいは&myString{"foo"}でさえ、かなり明確だ。
コメント 24
著者: creker | 投稿日: 2018-03-31
@bcmills 区別された構文が必要な理由はわかったが、これらの
{}の例はCの構造体初期化に似すぎている。構造体リテラルのアドレスを取っているように見えるのに、実際には構造体リテラルではないのは紛らわしい。
コメント 25
著者: cznic | 投稿日: 2018-03-31
構造体リテラルのアドレスを取っているように見えるのに、実際には構造体リテラルではないのは紛らわしい。
ある視点から見れば、それは構造体リテラルだ。
&int{42}は&(&struct{ i int }{42}).iの[省略形として]見ることができる。これは今日でも問題なく動作する: https://play.golang.org/p/dsaYvDmfGAH 。他の型についても同様だ。
コメント 26
著者: mwielbut | 投稿日: 2018-12-24
これを十分な回数やったので、本当にシンプルな(そしてたぶんバカバカしい)ヘルパー関数のパッケージを作った: https://godoc.org/github.com/mwielbut/pointy
コメント 27
著者: jaeyeom | 投稿日: 2019-06-25
これを十分な回数やったので、本当にシンプルな(そしてたぶんバカバカしい)ヘルパー関数のパッケージを作った: https://godoc.org/github.com/mwielbut/pointy
全然バカバカしくない。十分に痛みがあるので、
protobufパッケージにもこういったヘルパー関数がある。&vや&T(v)が仕様に追加されることを願っている(そしてできればnewキーワードを削除する)。
コメント 28
著者: icholy | 投稿日: 2019-06-25
「アドレスのコピー」構文が「任意の式のアドレス」と視覚的に区別される必要があるという議論を裏付けていると思う。
@bcmills 詳しく説明してもらえないか? 「アドレスのコピー」構文は存在しないと思っていたが。
コメント 29
著者: bcmills | 投稿日: 2019-06-25
@icholy、構文
&(*x)は今日、
xと同じ値に評価される(https://play.golang.org/p/4GS5_Z9B3HN)。&(*x+1)が、
+1が式に追加されたり削除されたりするだけで、エイリアシングの振る舞いが劇的に変わる ― 既存の値のエイリアスから新しい値の確保に変わる ― のは混乱を招くだろう。
コメント 30
著者: preciselytom | 投稿日: 2020-01-24
これがあれば私の人生が楽になり、コードがきれいになるだろう。構造体やフィールド引数で
*string、*uint64などを「オプショナルな値」として多用するライブラリがあり、リテラルを指定するためにいつもこういった「ヘルパー関数」を書くはめになる:func stringPtr(s string) *string { return &s }
コメント 31
著者: beoran | 投稿日: 2020-02-21
#37302 からここに来た。これは本当にGoの煩わしさだ。みんな定数をポインタに変換するための小さなヘルパー関数を書いている。例えば
intPtr(v int) *int { return &v}のようなものだ。これらの関数はあちこちにコピーされており、Go標準ライブラリの中にさえある! ほとんどがテストで使われている: 例えば src/encoding/asn1/asn1_test.go、src/encoding/json/decode_test.go など。定義はしばしば冗長で、名前さえ異なることがある。
これらの関数の代わりに、標準ライブラリも含めてこれらの小さなヘルパー関数を排除するために、この
&type()構文が本当に必要だ。
コメント 32
著者: benhoyt | 投稿日: 2020-02-21
この提案をどうやって前に進めるか、賛否を決めるにはどうすればいいか? 具体的には
&T(v)構文のことだ ― @ianlancetaylor が言うようにnew(T, v)は不要だと同意する。&int(42)のようなフレーズは明確で曖昧さがなく、名前付き一時変数を使った不格好な複数行のコードを避けるためにこの構文が有用だと多くの人が感じるだろう。シリアライゼーションライブラリなどに何らかのIntPtrスタイルの関数があることがその証拠だ。Ianが以前まとめたように:
私が思うに、ここでの提案は以下のようにすべきだ: 任意の型T、Tに代入可能な任意の値vに対して、式&T(v)を言語に追加する。この式は型Tの新しい変数を確保し、値vに設定し、そのアドレスを返す。
(別途、
&function(...)、例えば&time.Now()を検討できるが、それはあまり一般的には有用ではなく、別途検討できるだろう ― #22647 も参照。)
&T(v)の議論をフォローし、合意に至る ― Goの後続バージョンに含めるか、価値がないと判断するか ― 最善の方法は何か? ユースケース/経験報告への参照を含む正式な提案か? golang-devでの議論か?
コメント 33
著者: ianlancetaylor | 投稿日: 2020-02-22
上記の議論に明確なコンセンサスは見えない。
&T(v)に対する絵文字投票は良いが、他のアプローチを提案するコメントもかなりある。また、ジェネリクスの設計ドラフトが以下を書くことを許可するという点も検討する価値がある:
package addr func P(type T)(v T) *T { return &v }これは以下のように使える:
p1 := addr.P(1) // p1の型は*int p2 := addr.P(int64(2)) // p2の型は*int64 p3 := addr.P("hi") // p3の型は*string p4 := addr.P(time.Now()) // p4の型は*time.Timeこれにはいくつかの利点がある。新しい言語機能を必要としない(まあ、ジェネリクス以外の言語機能を必要としない)し、不要な場合に型を書く必要がない。
だから個人的には、ジェネリクスを得るまで待って、そのようなアプローチが十分かどうかを見たいと思う。
コメント 34
著者: benhoyt | 投稿日: 2020-02-22
もっともだ、ありがとう。ジェネリクスの設計が今後数年以内にどこかに向かっていると仮定して、それを待つことに満足している。:-)
コメント 35
著者: networkimprov | 投稿日: 2020-02-22
@ianlancetaylor、ポインタを作成するための3番目の構文を好むのか?
p := &v // 任意の変数に対して p := &T{...} // composite型に対して; &vと一貫性がある p := new(T) // 任意の型に対して、ただし主にプリミティブ p := addr.P(v) // 任意の値に対して、ただし主に定数これは言語の提案に関してよく提起される教育/認知的負担を増やす。
&T(v)は現在の構文と一貫性がある。それは大きく評価されるべきだ。@griesemer 何か考えは?
コメント 36
著者: griesemer | 投稿日: 2020-02-22
@networkimprov @ianlancetaylor が先ほど述べたように、まだ明確なコンセンサスは出ていない。これを解決できれば嬉しいが、緊急性はないと同意する。本当に説得力のある解決策か、既存の提案のいずれかで前に進むべき強い理由が出てくるのを待ちたい。私の知る限り、これは何もブロックしていない。
そして念のために言うと、3番目の構文を追加するのは良い計画ではなさそうだ。物事をよりシンプルで明確にしたいのであって、より複雑にしたいのではない。
コメント 37
著者: ConradIrwin | 投稿日: 2020-05-22
ここで別のアプローチの可能性を提案したい。
既存のセマンティクスに基づいて任意の式のアドレスを取ることが紛らわしいようなので、スコープを縮小して代わりに2つの具体的な変更を行うのはどうか:
- 関数の戻り値(関数が単一の値を返す場合)をアドレス可能なもののリストに追加する。これにより
&a()や&time.Now()が可能になる。- 型キャストの結果をアドレス可能なもののリストに追加する。これにより
&int(1)、&string("a")が可能になる。これにより最小限の言語仕様変更でほとんどのユースケースをカバーでき、新たに曖昧なケースを導入することもないと思う。また、構文の意味は
&演算子を再利用してポインタを作成するので、読み手にとって理解しやすいと思う。主な欠点は、型キャストなしで&1が動くべきだと人々が思い込むかもしれないことだが、これはコンパイルエラーを更新して「1のアドレスを取れません。アドレスを取るには明示的なキャストを使ってください:&int(1)」と言うことで解決できる。私がこれらの演算子を使いたいと思う主な場面は、オブジェクトリテラルを構築していてオブジェクトがポインタ値のフィールドを持つ場合だ(これは今日主にNULL許容カラムを持つSQLテーブルをモデリングするときに起こるが、不在と存在を区別するさまざまなAPIでも)。新しいGoプログラマーとして私は一時変数を作成していたが、その後ヘルパー関数のセットを書くようになった(#38298と同様)。
以下も検討したが、より複雑に思えたので見送った: 上記の2.の代わりに、2. 定数の振る舞いを拡張して、定数リテラルに対する
&がリテラルのデフォルト型へのポインタのデフォルト型を持つ定数を返し、定数と同じように文脈から確定型を得るようにする。これによりi := &1でiを*intに設定できるが、var x *float64 = &1も許される。主な利点は&1が&int(1)より短いことだが、欠点は&(2*math.PI)が動くことを期待し始めるかもしれないことだ(動かない)。上記の提案なら&float64(2*math.PI)は動く。また、pointersパッケージの追加(他の提案のような)は見送った。この変更は任意の型(私にとって重要なtime.Timeなど)に対して動作し、なぜ戻り値のアドレスを取れないかで混乱している初心者にも役立つからだ(私も含めて)。そしてGoにもう一種類のオプショナル構文を追加することも見送った。ポインタ値はオプショナルの概念的な一致であり、似たようなものに新しい構文は必要ないと思う。(それはgo2の関心事でもありそうだ!)
コメント 38
著者: Thor-x86 | 投稿日: 2020-11-18
#42690 から来た。@beoran と同じ問題だ。nilと非nilの値を同質な型の変数で扱うのを助けるライブラリを書いた。しかし、暗黙の変換が禁止されているため、大きな利点を追加しない。そのため、以下のようなワークアラウンドコードを書く必要がある:
myString := "Hello Gophers!" myNullableString := nullable.NewString(&myString)パラメータとして直接ポインタを割り当てるこのような書き方の代わりに:
myNullableString := nullable.NewString(&string("Hello Gophers!"))そして簡潔さの観点から、このアプローチは最初に「ホーム変数」を宣言するよりも比較的シンプルだ。行数が少なく、初心者にも理解しやすいように思える。さらに、
AND演算子 (&)との構文の衝突も見当たらない。
コメント 39
著者: ianlancetaylor | 投稿日: 2021-04-19
これらのアイデアは新しい提案 #45624 で再び取り上げられている。
コメント 40
著者: ianlancetaylor | 投稿日: 2023-08-23
このissueを #45624 を優先してクローズする。