Skip to content
AIこの記事はAIによって生成されたコンテンツです。

Go Issue #45624 全コメント翻訳 ― Rob Pikeが提起した「単純型のポインタ生成」10年の議論

golang/go#45624 は、2021年4月にGoの共同設計者Rob Pike自身が提出した「単純型へのポインタ生成式」の提案だ。2014年の #9097 が「かなりあっさりと」却下されたことを受け、別のアプローチで問題を再定義した。

4年半にわたる274件のコメントを経て、2025年9月に new(expr) として正式に採用され、Go 1.26(2026年2月)で実装された。

この記事では、本文と全274件のコメントを漏れなく日本語に翻訳する。

注: 本issueは非常に長大(274コメント)であるため、原文を忠実に翻訳しつつ、botによるCL(Change List)言及メッセージは簡潔に記載する。


Issue本文

著者: robpike | 投稿日: 2021-04-19

タイトル: spec: expression to create pointer to simple types

(最新の提案は https://github.com/golang/go/issues/45624#issuecomment-3250959795 を参照; --adonovan)

この概念は #9097 で取り上げられたが、かなりあっさりと却下された。再オープンするのではなく、別のアプローチを取ろう。

composite literalへのポインタを構築する方法として&S{}が言語に追加されたとき、私にはしっくりこなかった。アロケーションが半分隠されていて、魔法的だった。しかし慣れてしまい、今ではもちろん頻繁に使っている。

それでもまだ少し気になる。なぜならこれは特殊ケースだからだ。なぜcomposite literalに対してだけ有効なのか? これには理由があり、後で触れるが、構造体へのポインタを作るほうが:

go
p := &S{a:3}

単純型へのポインタを作るより簡単というのは、やはりおかしいと感じる:

go
a := 3
p := &a

この非対称性に対する2つの異なる解決策を提案したい。

定数へのポインタを許可することが繰り返し提案されてきた:

go
p := &3

しかしこれには3が型を持たないという厄介な問題があり、うまくいかない。

ただし、うまくいく2つの方法がある。

Option 1: new

newにオプションの引数を追加できる。考えてみれば、p := &S{a:3}p := new(S); *p = S{a:3} の省略形と見なせる。newに第2のオプション引数を許可すれば:

go
p1 := new(int, 3)
p2 := new(rune, 10)
p3 := new(Weekday, Tuesday)
p4 := new(Name, "unspecified")

さらに、&演算子をアドレス不可能な型付き式に適用した場合を new(typeOfExpression, Expression) として再定義することもできる。

Option 2

型変換(conversion)がアドレス可能であると定義する。型変換は常に新しいストレージを作成する必要があるため、これにより別のメカニズムで定数3の型を定義できる:

go
p := &int(3)

議論

個人的には両方のメカニズムに魅力を感じるが、どちらか一方でも問題を解決できる。したがって両方を行うことを提案するが、議論の結果1つだけ選ばれるかもしれない。

ラベル: LanguageChange, Proposal, Proposal-Accepted, release-blocker


コメント 1

投稿者: seebs | 投稿日: 2021-04-19

new() に型または明確な型を持つ式のどちらかを受け取れるようにしたら、どの程度互換性が壊れるでしょうか?つまり、new(int)new(fnReturningInt()) 、あるいは new(int(3)) のようなことができるようにし、new(3) は明確な型を持たないため許可しない、というものです。これで冗長な型の繰り返し問題は解決できるのではないでしょうか?

アドレス不可能なものからアドレスを取得する際の暗黙的なアロケーションには反対です。なぜなら、これは結局「ループ変数のシャドウイングとゴルーチン」の問題を、「mapの要素のアドレスを取得したが、書き込んでも変更されない」という最もよくある質問に置き換えるだけだと思うからです。ただし、&conversion() の場合にのみ発生するなら、かなり明確です。変換は論理的に新しいオブジェクトを作成していることが明らかであり、たとえ既にまったく同じ型に変換する場合でもそうです。

私の知る限り、オブジェクト名と型名は同じ名前空間にあり、Cのstruct-tagの混乱のようなものではなく、任意の時点で与えられた識別子はどちらか一方のみを参照します。

コメント 2

投稿者: clausecker | 投稿日: 2021-04-19

あるいは、int{3} のような単純型の複合リテラルを追加することの何が問題なのでしょうか?

コメント 3

投稿者: JAicewizard | 投稿日: 2021-04-19

この新しい new(typeOfExpression, Expression) があった場合、new(int32, int64(5)) のようなことは可能でしょうか?必ずしもこの具体例ではなく、指定された型に一致しない任意の式に対して暗黙的な変換が行われるのでしょうか?

個人的には new のアイデアの方が好きです。アドレス不可能な値のアドレスを取得する際に何が起きているかがより明示的だからです。

int{3} の追加は、解決策というよりも回避策のように感じます。問題を解決するためだけに、同じことを行う新しい方法を追加するということです。

コメント 4

投稿者: faiface | 投稿日: 2021-04-19

2つ目のアプローチを一般化して、関数の戻り値のアドレスを取得できるようにするのはどうでしょうか?

go
p := &f(...) // 任意の f に対して

型変換は特殊な関数にすぎないので、これでカバーできます。

コメント 5

投稿者: eaglebush | 投稿日: 2021-04-19

単純型を扱い、値で初期化するための new 提案オプションが気に入っています。このためだけに関数を作成してきました。この提案が承認されれば、構文はこのようになります:

go
i := new(int, 42)

...パッケージを前置するこのようなコードよりもずっと短くなります:

go
i := stdutil.NewInt(42)

さらに...

go
i := new(int, func() int {
   r := rand.New(rand.NewSource(99))
   return r.Int()
}())

コメント 6

投稿者: peterbourgon | 投稿日: 2021-04-19

私はnewビルトインについてほとんどの人よりも好意的です。規則的で使いやすく、少し冗長なだけです。しかし、多くの人はなぜか好きではないようです。

可能な場合は new より &T{...} を好みます。なぜなら、構築と初期化を単一の式で行えるからで、これは重要だと思います。それが機能しない唯一のケースは、この提案の2番目のオプションで対処されています。素晴らしい!賛成です。

私の理解では、これにより new ビルトインを使わずにすべての有効なGoプログラムを記述できるようになります。ボーナスチャレンジ:make に対しても同様のことをしましょう 😃 以下の4つをカバーできるように構造体リテラルの初期化構文を何らかの形で拡張することに帰着するでしょう:

make(chan T, n)
make(map[T]U, n)
make([]T, n)
make([]T, n, m)

コメント 7

投稿者: benhoyt | 投稿日: 2021-04-19

@peterbourgon おそらく make の廃止についてはこの議論を脱線させるべきではないでしょう 😃 私も &int(3) の型変換構文を好みます。私も new をほとんど使いません。new嫌いなわけではなく、通常必要ないからです。また、(#9097 以外の)過去の関連議論へのリンクも貼っておきます:

  • 数年前に似た issue を開きました (#22647)。これは簡潔な「経験レポート」が説明に含まれているので参考になると思います。例えば、AWS SDKにはこの機能の欠如を回避するために aws.Intaws.String のような関数があります。
  • new(value) を許可する2014年の提案もあります(こちらのGoogle doc)。new(T, value) ほど良くも直交的でもないと思いますが、歴史として参照リンクを載せておきます。

以前は &expression(Robのオプション「1a」?)に賛成でしたが、今は懸念が多すぎると思います。例えば、& が式と変数で異なる意味を持つことになります:&expression は常に新しいアドレスを返しますが、&variable は常に同じアドレスを返します。これは直感的ではないように思えます。これに関連して、@seebs が指摘したように &m[k] と書けてしまい、mapのエントリがアドレス可能に見えますが、実際にはそうではありません。これらの理由から、簡潔で良いにもかかわらず、単純な &expression は良くないアイデアだと思います。

コメント 8

投稿者: faiface | 投稿日: 2021-04-19

@benhoyt & を変数と関数呼び出しのみに制限し、任意の式には適用しないなら、かなり一貫性があります。関数呼び出しの結果は当然新しいアドレスを持つからです。

コメント 9

投稿者: benhoyt | 投稿日: 2021-04-19

@faiface はい、それなら問題ないと思います。&arbitraryExpression で指摘した問題はありません。私の issue #22647 は実際、Goを始めたばかりの頃に &time.Now() と書こうとしたことがきっかけで生まれました。

コメント 10

投稿者: clausecker | 投稿日: 2021-04-19

戻り値のアドレス取得をサポートする場合、戻り値を返す際にコピーが発生するかどうかという問題が生じます。例えば、次のようなコードを考えてください:

func addressTaker(x int, z **int) (y int) {
    y = x
    *z = &y
}

func example() {
    var ptr *int
    x := &addressTaker(42, &ptr)

    // この時点で x == ptr は成り立つか?
}

@benhoy new(value) の提案にはあまり賛成ではありません。パーサーで型と式を区別する必要があるという厄介な問題が発生するためです(少なくともそのように見えます)。

コメント 11

投稿者: clausecker | 投稿日: 2021-04-19

明らかな &int{3} というアイデアが言及されていないのも少し不思議に思います。とはいえ、型変換には引数の型がより柔軟であるという明白な利点(あるいはデメリット?)があります。両方の使い方をサポートするのも合理的かもしれません(型変換を行いたい場合用。変換がない場合は go vet を出す可能性あり)。もう一方は型変換を行いたくない場合用です。

コメント 12

投稿者: mcandre | 投稿日: 2021-04-19

Rob、そんなギャップのことは言わないでください。私はBlissのインターフェースをずっと実装していたんですから。

コメント 13

投稿者: robpike | 投稿日: 2021-04-19

@clausecker なぜ既存の構文を使えるのに、新しい構文(&int{3})を追加するのでしょうか?

コメント 14

投稿者: clausecker | 投稿日: 2021-04-19

@robpike 複合リテラルも既存の構文であり、そのアドレスを取得することは既に合法です。ですから、&int(3) のアイデアと同程度に「新しい構文の追加」です。どちらの場合も、以前は許可されていなかったケースをサポートするためにルールをより寛容にする必要があり、構文的な変更はありません。&int(3) の場合はアドレス取得を合法にする必要があり、&int{3} の場合はスカラー値に対して複合リテラルを使えるようにする必要があります。

コメント 15

投稿者: ninedraft | 投稿日: 2021-04-19

new(T, value) のバリアントには不快な特徴があります:真偽値や文字列値の場合、視覚的なノイズが過剰になります。例えば:new(bool, true)new(string, "bottle of ram")

私の理解では、明確な型推論に問題があるのは数値リテラルだけです。

上記を踏まえると、文字列や真偽値の場合に型を省略できるのであれば、& + 型キャストの方がより実用的なアプローチだと思います。

例:

go
_ = &int(42)
_ = &true
_ = &"brains"

type Name string
_ = &Name("what's my name?")

type Count int64
_ =&Count(100500)

コメント 16

投稿者: thejerf | 投稿日: 2021-04-19

Go 2のPlaygroundではこの関数が動作します

func PointerOf[T any](t T) *T {
	return &t
}

この issue をケースごとに分解すると、「モジュール内でこれが必要になることはゼロ回」(圧倒的に多いケース)、「1〜2回必要」この場合は数行余分に書くだけ、そして「あちこちで必要」この場合はジェネリクスが出たら関数を定義するかどこかからインポートすればよい、のいずれかになります。これを頻繁に使う場合は PointerOf よりも短い名前を好むかもしれませんが、長さよりも最大限の明確さを目指しました。

ジェネリクスがリリースされるのを待って、その関数を書く/提供するだけでよいと提案します。

コメント 17

投稿者: zkosanovic | 投稿日: 2021-04-19

@ninedraft

上記を踏まえると、文字列や真偽値の場合に型を省略できるのであれば、& + 型キャストの方がより実用的なアプローチだと思います。

しかし、型を省略することはできません。説明には型変換がアドレス可能になるとはっきり書かれており、値そのものがアドレス可能になるわけではありません。

こう書く必要があります:

go
_ = &bool(true)
_ = &string("brains")

正直に言えば、それで構いません。&"foobar" のようなものは少し...奇妙に感じます。

いずれにせよ、オプション2があればとても素晴らしいと個人的に思います。

コメント 18

投稿者: smasher164 | 投稿日: 2021-04-19

ジェネリクスがリリースされるのを待って、その関数を書く/提供するだけでよいと提案します。

ジェネリクスで PointerOf 関数を書けるのは確かですが、この(2番目の)提案の方が言語の学習をはるかに容易にすると思います。複合リテラルではファーストクラスの構文があるのに、それに対して関数を書かなければ/使わなければならないのは直感に反します。

コメント 19

投稿者: sanggonlee | 投稿日: 2021-04-19

意見を述べさせていただくと、オプション1よりもオプション2の方がはるかに好みです。 単純型リテラルがより深い基底型を持つという事実は、便利な構文の裏に隠されていました(例えば、3 はデフォルトで int 型ですが、int32 にもなり得ます)。 オプション1の構文は、型と式を別々の引数として渡すのが少し不自然に感じます。両者は本質的に結びついているにもかかわらずです。 技術的にはオプション2も同じですが、この場合は少なくとも &int32(3) において 3int32 型に属するという視覚的な手がかりが強く、広く使われている型変換の形式とより一貫しているように見えます。

コメント 20

投稿者: sethvargo | 投稿日: 2021-04-19

実際の使用における new()&{} の使用データはあるでしょうか?経験的に(そしてスレッドの他の方々にも支持されていますが)、&{} の方が new よりもはるかに一般的だと感じますが、それを裏付けるデータがあれば素晴らしいです。

間違いなくオプション2(&int64(11))を好みます。

コメント 21

投稿者: rh-kpatel4 | 投稿日: 2021-04-19

&(int64(11)) ではだめでしょうか?これは () の出力を取得し、そのポインタ &() を返すという適切なスコープ指定です。

コメント 22

投稿者: FiloSottile | 投稿日: 2021-04-19

2つ目のアプローチを一般化して、関数の戻り値のアドレスを取得できるようにするのはどうでしょうか?

go
p := &f(...) // 任意の f に対して

確かに、型変換と関数呼び出しの違いは理解していますが、Goを学んでいる人が &int(3) は動くのに &add(1, 2) は動かないことに困惑するだろうと感じます。

関数呼び出しには定義された型があるので、そのポインタを取ることに問題はないと思いますし、実際にコンパイラに許可されていないことを何度か思い出させられました。

単にやり方が2つあるところで選択したくないので new() は使いません。そのためオプション2だけにすることに傾いていますが、オプション1の最後の部分は & の完全性としてより良い着地点だと感じます。

コメント 23

投稿者: earthboundkid | 投稿日: 2021-04-19

Jerfの PointerOf 関数は言語自体に何も追加しない点が気に入っています。これは newofnewval などとしてビルトインに追加することもできるでしょう。上記のaddressTakerの例では、xに新しいポインタをアロケートし、曖昧さがありません。

コメント 24

投稿者: bcmills | 投稿日: 2021-04-19

提案されたすべてのオプションは現状よりも良いように見えますが、値から型が明らかな場合でも型を明示的に書く必要があるという欠点があります。比較してみてください:

go
	d := time.Millisecond
	p1 := &d  // 型のノイズなし!

vs.

go
	p1 := new(time.Duration, time.Millisecond)
	p2 := &time.Duration(time.Millisecond)

対照的に、ジェネリクスによるアプローチ (https://github.com/golang/go/issues/45624#issuecomment-822487722) は型の繰り返しがありませんが、ジェネリック関数の新しい名前を導入する必要があります。

そこで、代わりにジェネリックなビルトインを追加する方が好ましいのではないかと思います:

go
	d := ptrTo[time.Duration](time.Millisecond)

または

go
	d := ptrTo(time.Millisecond)

具体的な名前についてはこだわりませんが、ジェネリック関数の使い勝手は提案された new の使い勝手よりもずっと良いと思います。

コメント 25

投稿者: fkarakas | 投稿日: 2021-04-19

最初に @chai2010 が最初の提案をしたときは「3番目の構文を追加するのは良い計画ではない」と見なされましたが、Rob Pikeが提案すると素晴らしいということになるのですね!!!つまりGoのメンテナーは好きなようにできるということですか...


コメント 26

投稿者: clausecker | 投稿日: 2021-04-19

&foo(x) 構文について注意すべき点の一つは、設計上、型の不一致を検出できないことです。例えば、誤って別の列挙型に属する定数を使用した場合、明示的な型キャストが含まれているため、コンパイラはそれを検出できません。もし &foo{x} であれば(追加オプションとしてサポートされる可能性がありますが)、コンパイラはそのようなコードを型が間違っていると拒否できます。この機能に必須の準暗黙的なキャストを本当に導入したいのでしょうか?

コメント 27

投稿者: rsc | 投稿日: 2021-04-19

& を「既存の値のアドレスを取得する」と「複合リテラルのコピーを割り当てる」の両方にオーバーロードしていることは、常に不幸なことでした。& のオーバーロードを拡張する代わりに、new をオーバーロードして、元の new(T) だけでなく汎用的な ptrTo 関数としても機能させるという方法があります。例えば new(1) のように使います。そうすれば &T{...} は事後的に new(T{...}) の単なるシンタックスシュガーとして説明できます。

コメント 28

投稿者: rsc | 投稿日: 2021-04-19

@fkarakas:

最初に @chai2010 が提案した時は「3つ目の構文を追加するのは良い計画ではない」とされたのに、Rob Pikeが提案すると素晴らしいとされる!!!つまりGoのメンテナーは好きなようにできるということですね...

参考までに言うと、Robはあなたが言及した提案を明確にクレジットし、早すぎる段階で閉じられたと考えていると述べています。新しい証拠が到着したときに考えを変えないこと、過ちを犯した可能性を認めないことよりも、議論を再開する方が明らかに良いことです。私は https://blog.golang.org/toward-go2 の講演で、文脈について、そして追加の文脈や新しい経験が異なる結果につながりうることについて詳しく話しました。誰も完璧ではありませんし、アイデアが採用されるかどうかは、アイデア自体の詳細と同様に、そのアイデアの時期が適切かどうかにも依存します。よろしくお願いします。

コメント 29

投稿者: randall77 | 投稿日: 2021-04-19

&int{1} の問題点として私が感じるのは、単なる int{1} が何を意味するのかという疑問が生じることです。int(1) と同じなのでしょうか?同じことを言うのに2つの方法があるのはなぜでしょうか?

コメント 30

投稿者: rsc | 投稿日: 2021-04-19

&foo{x} のもう一つの問題は、foo と x がどちらも []interface{} 型の場合に何を意味するかということです。その場合、2つの異なることを表現する方法が1つしかなくなります。

コメント 31

投稿者: Laremere | 投稿日: 2021-04-19

この提案はこの動作をリテラルに限定しているのでしょうか?使用されている形式は明らかに限定されていません。限定されていない場合、この提案に基づく複数の例を比較・対照してみましょう:

現在のGo:

go
a := 1
b := &a
*b = 2
fmt.Print(a,*b) // 2 2

ポインタへのキャストを使用する場合:

go
a := 1
b := &int(a)
*b = 2
fmt.Print(a, *b) // 1 2

拡張されたnewを使用する場合:

go
a := 1
b := new(int, a)
*b = 2
fmt.Print(a, *b) // 1 2

ジェネリクスによるptrTo:

go
a := 1
b := ptrTo(b)
*b = 2
fmt.Println(a, *b) // 1 2

これら3つの例はすべて、ローカル変数へのポインタを取得することとは微妙に異なる動作を示しています。より複雑な文脈でこの新しい構文を使用することは合理的でしょう(つまり、例の各文の間により多くの行がある場合)。初心者やこの構文に馴染みのない人にとって、コードが異なることをしていること、そしてその違いが何であるかが明白であることが重要です。

キャストはこのテストで最も成績が悪いと感じます。キャストは関数呼び出しのように見えるかもしれませんが、関数呼び出しのようには感じません。そのため、この提案はその動作の詳細について新しい領域を開拓しています。 拡張されたnewはこのテストでより良い成績を出し、ptrToが最も良いと思います。これらは単に関数呼び出しに値型を渡すという慣例に従っています。


あるいは、

定数へのポインタを許可することが繰り返し提案されてきました。例えば p := &3 しかし、これには3が型を持たないという厄介な問題があるため、うまくいきません。

これがなぜうまくいかないのか、私には明らかではありません。

以下で型を指定しない場合、

v := 3
p := &v

なぜ p := &3 には型が必要なのでしょうか?型を指定せずに変数を宣言する場合と同じルールを使用することの何が問題なのでしょうか?

コメント 32

投稿者: rsc | 投稿日: 2021-04-19

p := &3 がうまくいかないのは、狭い形式のセットに限定しなければならないからです。そうでなければ、&f().x の意味が、f() がポインタ型の構造体を返す場合と構造体を返す場合で異なってしまいます。同様に、&m["x"] は今日ではコンパイルエラーですが、マップ内の値へのポインタを生成するのではなく、明日には暗黙的にコピーを作成することになります。これらすべては非常に混乱を招き、多くの微妙なバグの原因となるでしょう。

コメント 33

投稿者: seebs | 投稿日: 2021-04-19

まあ、この件全体がばかげていることが分かりました。x := &int{3} と書こうとするときに人々が意味することを実現する、完全に透過的で入力しやすい方法が既にあるのです:

x := &((&[1]int{3})[0])

「非複合型に対して {} イニシャライザを許可し、暗黙的にその型の [1] として扱う」ことに一票を投じたいと思います。(ただし、[]interface のようなケースでの困難は理解しています...)

しかしそうすると、キーを指定することが許可されるべきかという疑問が生じます:

x := &int{0: 3}

コメント 34

投稿者: xaionaro | 投稿日: 2021-04-19

個人的には以下を使用しています:

p := &[]int{v}[0]

[]int{v}[0]{v} に省略できるようにするのが理にかなっているかもしれません:

p := &{v}

したがって:

  • p := &{v}v のコピーを指します。ここで v は何でもよく、例えば myFunc(): p := &{myFunc()}、または 3.1416: &{3.1416}f := 3.1416float64 であるため float64* になります)。
  • p := &vv 自体を指します。

ただし、Goがシンタックスシュガーを重視する言語かどうかは分かりません。

コメント 35

投稿者: seebs | 投稿日: 2021-04-19

昔々、C標準は「まあ、明らかに、ある型の任意のオブジェクトはその型の要素1つの配列でもある」と言って多くのことを手を振って済ませていました。そのため、あちこちで括弧付きイニシャライザが許可されていました:

int i = (int){8};
int *ip = &(int){8};
int j = {8};

もちろん、彼らには interface{} を扱う必要がありませんでした。

現在、&literal{...} が動作する理由は、ガベージコレクションとエスケープ解析の微妙な副作用のようなものです。オブジェクトを宣言することが許可されており、エスケープする場合はヒープに割り当てられるので、ポインタがエスケープすると割り当てが発生し、そうでなければ他の変数宣言と同様に「実際の」割り当てではなく、単にスタックアドレスを使用しているだけです。一方、new() はポインタがエスケープせずヒープ割り当てが不要な場合でも、「割り当てられる」という含みを持っています。

new(expr)p := new(T); *p = expr のように動作させるのはおそらく合理的で無害だと思います。そうであれば、new(expr) をより多く使い、&literal{} をより少なく使うかもしれません。なぜなら、何をしているのか、なぜそうしているのかがより明確になるからです。私が new をあまり使わない理由は、冗長で、ゼロ値オブジェクトの割り当てとその値の設定を区別する必要があるからです。

コメント 36

投稿者: Laremere | 投稿日: 2021-04-19

@rsc ありがとうございます、問題点が分かりました。これは私が聞いていたこととは少し異なりますが、より正確に質問を考えていくうちに、問題点が見えてきました。提案や問題が見えていない他の方のために、ここに記載します:

現在のGo仕様には次のように記載されています:

複合リテラルのアドレスを取得すると、リテラルの値で初期化されたユニークな変数へのポインタが生成されます。

この文から「複合」を削除することは合理的に思えます。つまり、任意の Literalここで定義)の値をリテラルの値へのポインタとして初期化できるようになります。

複合リテラル以外のリテラル値は、数値、ルーン、文字列(すべて BasicLit)、および関数(FunctionLit)のみです。

ルーン、文字列、関数はすべて明確に定義された型を持ち、問題なく動作するでしょう。カスタム型は少し不格好ですが、それ以外は問題なく動作します:

go
p4 := (*Name)(&"unspecified")

残るのは数値で、型が指定されていない場合に型を決定するための明確に定義されたルールが既にあります。例えば、&3*int になり、&1.2*float64 になります。しかし、byteへのポインタはどうやって取得すればよいのでしょうか?通常、数値定数を目的の型に解決するためにキャストが使用されます。しかし、&byte(3)Literal のアドレスを取得しているのではなく、キャストの結果のアドレスを取得しています。

数値の問題がなければ、これは現在の動作の合理的な拡張であり、複合リテラルの特別扱いを減らすことになると思います。& に2つの意味があることは変わりませんが、その一方がわずかに強力になるだけです。

(*byte)(&3) を許可することも_考えられます_。ここで &3 は「ポインタ数値リテラル」であり、通常の数値が解決されるのと同様のルールを使用して、特定の数値型へのポインタに解決されます。これは確かにメインの提案と同等かそれ以上の複雑さを追加しますが、数値リテラルのみに限定されます。良いと思うかどうかは分かりません。

コメント 37

投稿者: nemith | 投稿日: 2021-04-19

これがなぜ便利かという点について、thriftは生成されたコードでオプショナルフィールドにポインタを使用し、nilでフィールドが欠落していることを表現します(ゼロ値は選択肢にありません)。そのため、thriftライブラリにはリテラルをポインタ化するための多くの関数が含まれています。

https://github.com/apache/thrift/blob/master/lib/go/thrift/pointerize.go

今後ジェネリクスが使えるようになれば、オプショナルラッパー型やジェネリックなポインタ化関数で対処できるかもしれません。

コメント 38

投稿者: DeedleFake | 投稿日: 2021-04-19

私はこれを何度も望んだことがありますが、ほとんどの場合、構造体フィールドの初期化時など、オプショナルな値を設定したいときに望みました:

go
type Config struct {
  Address *string
}

// ...

c, err := CreateClient(Config{
  Address: &string("localhost:12345"), // 動作しないのは明らか。
})

ジェネリクスが導入されれば、この問題は時間の経過とともに自然に解消されるのではないかと思います。オプショナル性は技術的にはポインタの副作用に過ぎないからです。多くのものがポインタを返す代わりにbooleanを返してプライマリリターンの有効性を示すのもそのためです。しかしジェネリクスは、より適切にシグナルされたオプショナル性を作ることができます:

go
type Optional[T any] struct {
  v T
  ok bool
}

func Some[T any](v T) Optional[T] {
  return Optional[T]{v: v, ok: true}
}

func None[T any]() Optional[T] {
  return Optional[T]{ok: false}
}

type Config struct {
  Address Optional[string]
}

// ...

c, err := CreateConfig(Config{
  Address: Some("localhost:12345"),
})

そしてこれを書き終えた後、すぐ上に読み込まれた新しいコメントを見ました... @nemith、先を越されました。

コメント 39

投稿者: slrz | 投稿日: 2021-04-19

new の拡張は非常にすっきりとしていて良いですね。&expressionnew(typeOfExpression, expression) の省略形として導入しなくても、それだけで価値があるでしょう。

コメント 40

投稿者: travisjeffery | 投稿日: 2021-04-19

私は new にパラメータを追加する方を好みます。例えば new(int, 3) です。& をオーバーロードしないため、言語の観点からずっとすっきりとしてシンプルに見えます。

コメント 41

投稿者: mdempsky | 投稿日: 2021-04-19

どちらのオプションも好きで、両方追加することを支持します。ただしオプション1の中では、既に何人かが上で提案しているように、new(int, 3) よりも単に new(3) の方を好みます。

new() が明確な型を持つ型_または_式のどちらかを取れるようにした場合、どの程度影響がありますか?

何も壊れません。パーサーと型チェッカーは、e1 が型式か値式かに応じて e1(e2) が変換なのか関数呼び出しなのかを既に区別できる必要があります。

つまり、new(int)new(fnReturningInt())、あるいは new(int(3)) は可能だが、new(3) は明確な型を持たないので不可?

3 には明確な型があります:デフォルト型の int です。デフォルト型を持たないのは値 nil だけです。

コメント 42

投稿者: HALtheWise | 投稿日: 2021-04-19

オプション2、またはこのスレッドで何度か言及されたすべての関数呼び出しへの拡張を支持します。なぜなら、新しい動作を追加するのではなく、単に既存の制限を取り除くように最も明確に感じられるからです。Goは一般的に、引数の数によって動作が異なる可変長関数を推奨したり使用したりしません。「new」の2引数形式と聞くと、直感的に make() の複数引数形式のように動作すると期待します。make() は今日そのような奇妙な組み込み関数の唯一のものです。オプション1はそのようにはならず、結果としてほとんど使わないルールを覚えるため、または新しいユーザーがそれに遭遇した時に何を意味するか調べるための追加の精神的な負荷が生じます。

@rsc がみんなが &t{} 形式ではなく new() 形式に標準化していればよかったと思っていることは知っていますが、今日では後者の方がより一般的であるという感覚があり、それに逆らいすぎないようにすべきだと思います。

コメント 43

投稿者: icholy | 投稿日: 2021-04-19

以下の2つの形式を許可すれば、危険な罠なしにユースケースの大部分に対応できるでしょう:

&AnyLiteral
&Type(AnyLiteral)

@Laremere が指摘したように、これは仕様への最小限の変更でもあります。

コメント 44

投稿者: smasher164 | 投稿日: 2021-04-19

生成されたコードでオプショナルフィールドにポインタを使用し、nilでフィールドが欠落していることを表現します(ゼロ値は選択肢にありません)。

これは、GoのGraphQLやAvroライブラリの多くで採用されているのと同じアプローチです。シリアライゼーションやRPCフレームワークはこの問題に遭遇すると言っても過言ではありません。コードベースは結局、PtrTo[Int|Float64|...] のような関数をインポートするか再定義することになります。

コメント 45

投稿者: ysmood | 投稿日: 2021-04-19

こういうのはどうでしょうか:

go
type Cube struct {
    Size int
}

a := new(Cube{Size: 10})

b := new(10) // 型推論によりint

var c int64 = new(10) // コンパイラに望む型を伝える

d := new("string")

e := new(1.0) // float64

コメント 46

投稿者: rogpeppe | 投稿日: 2021-04-20

このコメントを少し繰り返しますが、Limboにはまさにそれを行う ref という組み込み関数がありました。ジェネリクス付きのGoでは、次のように定義できます:

// ref は t の値へのポインタを返します。
func ref[T any](t T) *T {
    return &t
}

この可能性を考えると、この機能に対応するために new や言語構文自体を変更する必要はないと思います。

まさにこのシグネチャと動作を持つ組み込み関数(ptr ?)を定義することで、提案されたものよりも人間工学的で柔軟な方法でこの提案の要件を満たすことができるでしょう(型名を言及する必要がなく、関数呼び出しを含む任意の式に対して問題なく動作します)。

ジェネリクスが言語に実装される_前_でも、問題なくそれを実現できるでしょう。

したがって、以下のすべてが動作します:

ref(123)   // *int
ref(make([]string, 0, 3))   // *[]string
ref("hello")  // *string
ref(ref(string))  // **string
ref(os.Stdin.Name())

参考までに、Goはポインタを「ref」と呼ばないLimboとは異なりますが、それでも ref という名前が好きです。 上記のplaygroundはこちらです:https://go2goplay.golang.org/p/UV0z1TxjxRh

コメント 47

投稿者: rogpeppe | 投稿日: 2021-04-20

参考までに、上記の解決策は2016年に提案しました。

コメント 48

投稿者: ziutek | 投稿日: 2021-04-20

@rsc と @ysmood の new 組み込み関数をオーバーロードする提案が言語に最もフィットすると思います。

x := new(int) が以下の省略形として扱えるなら、

var a int
x := &a

x := new(10) は以下の省略形になれます:

a := 10
x := &a

@ysmood、私の意見では

var c int64 = new(10)

は「型の不一致」エラーを返すべきです。

以下の場合はどうすべきか分かりません:

var c *int64 = new(10)

これにはおそらく型なしintポインタのようなものを言語に導入する必要があります。そのような追加なしでは、次のように書く必要があります:

var c *int64 = new(int64(10))

コメント 49

投稿者: davecheney | 投稿日: 2021-04-20

オプション2、i := &int(3) に一票を投じたいと思います。

protoがGoユーザーに与えた苦痛にもかかわらず、ユースケースはnewを拡張するほど一般的ではないと思います。

コメント 50

投稿者: ziutek | 投稿日: 2021-04-20

new のオーバーロードに関して私が感じる問題は:

A := 56
x := new(A)

x := new(A)A := 56 から離れた場所にある場合、Aが型なのか定数/変数なのか明らかではありません。


コメント 51

投稿者: carleeto | 投稿日: 2021-04-20

私は2パラメータ版のnewが好みです。

はい、少し多くタイプする必要がありますが、型について明示的にすることが求められるため、意図が明確になります:

go
x := new(int,3)
x := new(int8,3)
x := new(uint16,3)

そうは言っても、単にintが欲しいだけなら、1パラメータ版もサポートすることに問題はないと思います:

go
x := new(3)

あるいは、float64の場合も同様に:

go
x := new(3.4)

コメント 52

投稿者: rogpeppe | 投稿日: 2021-04-20

newの第1パラメータをオーバーロードすることにはあまり賛成できません。言語内で(私の知る限り)組み込み関数が型または式のどちらかを引数として取るケースは他にないからです。

また、ほとんどのユースケースで型名を記述せずに済むのに、newにオプションの追加パラメータを加えるという考えも好きではありません。

既に言語に提案されている内容だけでこの機能を実装できることを考えると、ジェネリックな組み込み関数を定義する「マジックを少なくする」アプローチを強く支持します。ジェネリクスが実際に導入されれば、さらにマジックは少なくなるでしょう。

ただし、名前の選択は難しいです:ptrToは他の言語組み込みがキャメルケースを使っていないので好みではありません。refは既存の命名に合いません。ptrは他では見られない短縮形を使っています(めったに使われないprintlnを除けば)。

コメント 53

投稿者: ruyi789 | 投稿日: 2021-04-20

int  x=1,y=2,z=3

または

var (x=1,y=2,z=3} int
if()//support it

コメント 54

投稿者: earthboundkid | 投稿日: 2021-04-20

名前の選択は難しいです:ptrToは他の言語組み込みがキャメルケースを使っていないので好みではありません。refは既存の命名に合いません。ptrは他では見られない短縮形を使っています(めったに使われないprintlnを除けば)。

自転車置き場の議論に陥るリスクを承知で言いますが、返される値は既存の値へのポインタや参照ではないため、newofnewvalが好みです。これは関数呼び出しによって作られたコピーへのポインタです。名前に_new_を含めることで、例えばptr := newof(mymap[key])が(不正な)ptr := &mymap[key]とは等価でないことが明確になります。なぜなら、新しいポインタだからです。

コメント 55

投稿者: urbanishimwe | 投稿日: 2021-04-20

&int(3)の方が好みです。 この構文を許可する必要があると思います:&Tp := &intのように、pのアドレスにあるvalueint型のゼロ値になります。 これは、既にnew(T)がほぼ同じことをしているからです。

コメント 56

投稿者: hardboiled | 投稿日: 2021-04-20

私たちのチームには、ユーザーからの設定データが多くあり、これらの設定データを使用する内部APIエンドポイントに似ていますが、完全には一致しません。

その結果、ポインタ値メンバーで構成された構造体に、特にテストでデフォルトの適切な値を割り当てる必要がある状況が多く発生します。Goは現在、非構造体のポインタ代入のインライン化をサポートしていないため、以下のような状況が生じます:

golang
// テストからの初期化文の例
multiAz := true
storageEncrypted := true
dbConfig := &DatabaseConfig{
	Identifier:       "id",
	InstanceClass:    "db.t3.small",
	InstanceType:     "mysql",
	EngineVersion:    "1.1.1",
	MultiAz:          &multiAz,
	StorageEncrypted: &storageEncrypted,
}

将来的には、このテストを以下の形式で更新できるとよいでしょう。

golang
dbConfig := &DatabaseConfig{
	Identifier:       "id",
	InstanceClass:    "db.t3.small",
	InstanceType:     "mysql",
	EngineVersion:    "1.1.1",
	MultiAz:          &bool(true),
	StorageEncrypted: &bool(true),
}

個人的には、私たちのコードの可読性とコード品質を確実に向上させると思います。この例が、なぜこれが役立つかを説明するのに他の方の参考になれば幸いです。そしてここでメリットについて議論してくださりありがとうございます!

コメント 57

投稿者: dolmen | 投稿日: 2021-04-21

編集:このコメントは https://github.com/golang/go/issues/45624#issuecomment-822594892 と重複しています

<details> <summary>元の内容</summary>

ポインタ式は任意の値に対して既に存在していることに注意してください。ただし見た目が醜いだけです。

例(Go Playground で確認):

go
// int64の値(3)へのポインタ
int64Ptr := &(&(struct{ int64 }{3})).int64

fmt.Printf("%T %v\n", int64Ptr, *int64Ptr)

myfunc := func() string { return "foo" }

// 関数が返した値へのポインタ
stringPtr := &(&(struct{ string }{myfunc()})).string

fmt.Printf("%T %v\n", stringPtr, *stringPtr)

編集:@mdempsky のおかげで、より短い式:

go
int64Ptr := &[]int64{3}[0]

</details>

コメント 58

投稿者: dolmen | 投稿日: 2021-04-21

よくある回避策は、各型に対する変換関数を公開するptrという名前のパッケージを使うことです。

go
boolPtr := ptr.Bool(true)

new(bool, true)がイディオムとして定着しているものより良いとは思いません。

例:

コメント 59

投稿者: xaionaro | 投稿日: 2021-04-21

よくある回避策は、各型に対する変換関数を公開するptrという名前のパッケージを使うことです。

それは不可能です。カスタム型を作成した場合、それはこれらのライブラリのどれにも知られていません。

コメント 60

投稿者: dolmen | 投稿日: 2021-04-21

それは不可能です。カスタム型を作成した場合、それはこれらのライブラリのどれにも知られていません。

カスタム型Tを作成するなら、そのポインタコンストラクタも作成できます:func ptrT(t T) *T { return &t }。そして、同じパッケージ内にある必要もありません。少しのコピーは少しの依存関係よりも良い。

コメント 61

投稿者: mdempsky | 投稿日: 2021-04-21

@dolmen

少しのコピーは少しの依存関係よりも良い。

その格言は、コード構造に関する決定が依存関係を追加するコストを考慮すべきだということについてです。あなたがここで適用しているように、コードをコピーすることが本質的に良いまたは望ましいと示唆するものではありません。その証拠として、この提案がそのフレーズを作り出した(あるいは少なくとも広めた)同じ人物によって提出されたことに注目してください。

コメント 62

投稿者: deanveloper | 投稿日: 2021-04-22

オプション2、i := &int(3)を支持したいと思います

protoがGoユーザーに与えた苦痛にもかかわらず、このユースケースはnewを拡張する必要があるほど一般的ではないと思います

これに同意します。多くの組み込み関数のシグネチャは既に読みにくく、特にオプションパラメータがある場合はそうです。newのシグネチャをmakeのようなものに汚してしまうのは残念です。newのシグネチャにオプション引数を追加するよりも、&を構造体で機能するように適応させる方がはるかに好ましいと思います。

正直なところ、newを完全に廃止したいです。個人的にはあまり好みではない組み込み関数です。組み込み関数は組み込み型に対する操作を定義するためや、ジェネリクスの欠如により定義不可能な関数のために使うのが最適だと考えています。newは一時変数を省略する以上の目的を果たさず、newを変数名として使うことを躊躇させます(これは使えると非常に便利な変数名です)。

コメント 63

投稿者: davecheney | 投稿日: 2021-04-22

カスタム型Tを作成するなら、そのポインタコンストラクタも作成できます

新しい型Tを宣言すれば、&T{}new(T)式を使ってTへのポインタを取得できます。

様々なptrパッケージが、言語自体を拡張することなく、20数個のユニバース型すべてに対してこの問題を解決できるように思えます。

コメント 64

投稿者: dolmen | 投稿日: 2021-04-22

@mdempsky 格言への私の言及は的を射ていました。go.uber.org/thriftrw/ptrのようなパッケージは、func ptrT(v T) *T { return &v }の定義の集まりに過ぎません。これらの関数は利便性のためにパッケージにまとめることも、依存関係を追加せずに必要な時にコード内でローカルに定義することもできます。

<details> <summary>編集:ジェネリックptrToに関する以前のコメントと重複する追加コメント(ただしコード例付き)</summary>

また、ジェネリクスがあれば、ptr関数を一度だけ定義して全てに対応できるようになります。

go
func ptr[T any](v T) *T {
	return &v
}

intPtr := ptr(3)
fmt.Printf("%T %v\n", intPtr, *intPtr)

floatPtr := ptr(3.14)
fmt.Printf("%T %v\n", floatPtr, *floatPtr)

xPtr := ptr(map[string][1]struct{bool; string; *int}{"foo": {{true, "bar", nil}}})
fmt.Printf("%T %v\n", xPtr, *xPtr)

go2go Playgroundで確認 </details>

コメント 65

投稿者: ayang64 | 投稿日: 2021-04-22

オプション2がリテラル(複合リテラルかそうでないかを問わず)のアドレスを取得する構文を一貫したものにする点がとても気に入っています。newを拡張したり、関数やキーワードを追加したりすると、その不一貫性が宙ぶらりんのまま残ると思うので、少なくともオプション2が前進することを望みます。

任意のリテラルのアドレスを取得できるならnew()を拡張する必要はないと思いますし、新しい関数やキーワードが必要だとも思いません。

コメント 66

投稿者: ayang64 | 投稿日: 2021-04-22

この構文を許可する必要があると思います:&Tp := &intのように、pのアドレスにあるvalueint型のゼロ値になります。 これは、既にnew(T)がほぼ同じことをしているからです。

私たちが話しているのは値のアドレスを取得することであり、型のアドレスではないと思います。型のアドレスを取得することがどう意味を持つのか分かりません。つまり、&演算子を型に適用した場合、それを何と呼ぶのでしょうか?また、既にfoo := (*string)(nil)があります。

コメント 67

投稿者: davecheney | 投稿日: 2021-04-23

@dolmen マップへのポインタのユースケースは何ですか?

go
xPtr := ptr(map[string][1]struct{bool; string; *int}{"foo": {{true, "bar", nil}}})

コメント 68

投稿者: kylelemons | 投稿日: 2021-04-23

オプション1a、new(typeOfExpression, Expression)

これは少し冗長なので、ヘルパー関数やライブラリが不要になるとは思いますが、2番目のパラメータを追加するほどの価値はないと思います。


オプション1b、p := &expression

これは便利ですが、アドレス可能性と直交性への影響が気になります。以下を考えてください:

a := &m[k]
a := &m.(T)
a := &f()

現在、これらはどれもアドレス可能ではありません。

GoのクラスでInterfaceを教える際、アドレス可能性について多くの時間を費やします。なぜなら、新しいGopherがインターフェースを満たすためにポインタが必要な場面で値を渡そうとするのをよく見かけるからです。この概念を理解することは重要だと思います。

簡単に言うと、通常以下のような部分があります:

以下のように書くとき
  t.M()

コンパイラが手助けできます:
   | (*T).M   |  (T).M
---+----------+----------
 T | (*t).M() | t.M()
*T | t.M()    | (&t).M()

しかし、値をインターフェースに格納する場合:

  var i interface{M()}
  i = t // メソッドセットに依存して合法な場合とそうでない場合がある
  i.M()

コンパイラが(概念的に)手助けできるのは一部の場合だけです:
   | (*T).M   |  (T).M
---+----------+----------
 T | (*i).M() | i.M()
*T | i.M()    | ????

コンパイラは(正しく)以下を書くことができません
  (&i).M()

しかし、この提案では「インターフェース内の値のアドレスを取得する」ことがある種合法になり、曖昧で非直交的になり始めると感じます。これにより、新しい人がメソッドセットとアドレス可能性を本当に理解することがさらに難しくなるかもしれません。


オプション2:&type(C)

_もし_ここに解決策が必要だと考えるなら、これが私の選択です。少し冗長ですが、構文の直交性を維持しており、&f()が「ヒープに既に存在する何かのアドレスに評価される」ように暗示するかもしれないのに対し、コードが新しく割り当てられたメモリ位置へのポインタを要求していることがより明確になります(私の読み方では)。

個人的には、少なくとも最初はコンパイル時定数に値を制限することを推奨します。


公式に提案の一部ではないので、他の提案についてはあまり触れませんが、簡潔に:

  • new(v)(つまりnewの第1パラメータを変更すること)は、新しい組み込みを追加するよりも厳密に劣ると思います。
  • ptr(v)のような新しい組み込みを追加してパッケージを置き換えるのは興味深いですが、その名前は既によく使われています。
  • ジェネリクスまで待って独自のジェネリックPtrToを作ることは問題なく、それらは既存のヘルパーと共存でき、特にそれらを頻繁に必要とするドメイン(protobufなど)で有用です。

コメント 69

投稿者: dolmen | 投稿日: 2021-04-23

@urbanishimwe の発言:

この構文を許可する必要があると思います:&T。 p := &intのように、pのアドレスにある値はint型のゼロ値になります。 これは、既にnew(T)がほぼ同じことをしているからです。

これは既存のコードの意味を変える可能性があります(あるいは少なくとも読者の心に混乱を加えます)。なぜなら、スコープ内に型と同じ名前の変数が存在する場合、このステートメントは既に有効だからです。したがって、これは良いアイデアではないと思います。

// このコードは現在動作します
var int int
p := &int

Go playgroundで確認

この例は作為的ですが、名前付き型と同じ名前の変数を持つことはもっとよくあります。

例:

// パッケージスコープにて:
type date string

// 関数の深いところで
var date time.Time
p := &date

コメント 70

投稿者: dolmen | 投稿日: 2021-04-23

@davecheney の発言:

マップへのポインタのユースケースは何ですか?

一般的な質問として:

go
var m map[string]bool
p := &m
json.Unmarshal([]byte(`null`), p)
json.Unmarshal([]byte(`{"foo": true}`), p)

Go Playground

この提案の文脈(非ゼロ値の式へのポインタ)では、func/interface/channel/pointerへのポインタと同様に、ユースケースが見当たりません。ニッチなユースケースすら見つけられませんでした。

コメント 71

投稿者: deanveloper | 投稿日: 2021-04-23

@urbanishimwe の発言:

この構文を許可する必要があると思います:&T。 p := &intのように、pのアドレスにある値はint型のゼロ値になります。 これは、既にnew(T)がほぼ同じことをしているからです。

これは既存のコードの意味を変える可能性があります(あるいは少なくとも読者の心に混乱を加えます)。なぜなら、スコープ内に型と同じ名前の変数が存在する場合、このステートメントは既に有効だからです。したがって、これは良いアイデアではないと思います。

// このコードは現在動作します
var int int
p := &int

Go playgroundで確認

この例は作為的ですが、名前付き型と同じ名前の変数を持つことはもっとよくあります。

例:

// パッケージスコープにて:
type date string

// 関数の深いところで
var date time.Time
p := &date

&int&dateは型ではなく依然として変数を指すのではないでしょうか?何も変わっていないと思います。

コメント 72

投稿者: rogpeppe | 投稿日: 2021-04-23

@davecheney

マップへのポインタのユースケースは何ですか?

マップへのポインタのユースケースはないかもしれませんが、スライスへのポインタのユースケースは確実にあり、同様の問題があります: https://play.golang.org/p/fXlCMX6EYUr

type S struct {
	// スライス自体がnon-nilでもSliceは省略される。
	Slice []int  `json:",omitempty"`
	// PtrSliceはnon-nilの場合にのみ省略される。
	// スライスが空でもそう。
	PtrSlice    *[]int `json:",omitempty"`
}

@kylelemons

ptr(v)のような新しい組み込みを追加してパッケージを置き換えるのは興味深いですが、その名前は既によく使われています。

あなたが思っているほど使われていないと思います。私の$GOPATH(約2500万行のGoコード)を調べたところ、235パッケージで識別子としてのptrの使用は1994件しか見つかりませんでした。実際に問題になると思いますか?

比較のため、refについても同じ検索を行い、1897パッケージで7059件の使用が見つかったので、その指標ではptrの方が優れているようです。

ptrToも検索しましたが、使用例は全くありませんでした。

コメント 73

投稿者: clausecker | 投稿日: 2021-04-23

この関数をdupと名付けて、汎用的な浅いコピー関数として動作させることもできます。これによりこのIssueの要件を満たしつつ、読者にとって機能が明白になります(引数のコピーを作成するため)。ただし、型なしオペランドに関する問題は残ります。

コメント 74

投稿者: jalavosus | 投稿日: 2021-04-23

@rogpeppe なぜスライスへのポインタが必要なのか少し混乱しています。スライスは既にnil可能だからです。これについて簡単なPlaygroundの例を作りました(変数名についてはご容赦ください、丸一日以上寝ていないので):https://play.golang.org/p/9XO6Q0ZI1mI。ここでは、`omitempty`付きのスライスを持つ新しくアンマーシャルされた構造体はnilになります。

コメント 75

投稿者: thejerf | 投稿日: 2021-04-23

「通常ポインタを取らないものへのポインタ」のユースケースは、atomic.CompareAndSwapPointerのパラメータとしてです。ニッチであることは間違いありませんし、チャネルポインタのスワップは悪い設計に聞こえますが、それ以外は起こり得ます。


コメント 76

投稿者: quenbyako | 投稿日: 2021-04-23

これは素晴らしい変更です。#12854 についても触れておきたいと思います。これらは非常に関連性のあるものです(つまり、ポインタが必要な値の型を明示的に指定する必要がない場合があるかもしれません)。

<details> <summary></summary> 反対票を押す人たちはこんな感じ: image </details>

コメント 77

投稿者: rogpeppe | 投稿日: 2021-04-24

@rogpeppe スライスへのポインタがなぜ必要なのか少し困惑しています。スライスはすでに nil にできるので。これについて簡単な playground の例を作りました(変数名については申し訳ありません、1日以上寝ていないので):https://play.golang.org/p/9XO6Q0ZI1mIomitempty が付いたスライスを持つ新しくアンマーシャルされた構造体は nil になります。

問題はデコード時ではなく、エンコード時に発生します(omitempty はデコード時には効果がありません)。

具体的には、スライスが nil の場合でも [] としてマーシャルすることが有用な場合があり、また、スライスの長さがゼロの場合にフィールドを含めるかどうかを選択しつつ、省略する機能も保持できると便利です。

コメント 78

投稿者: kylelemons | 投稿日: 2021-04-27

@rogpeppe ptr の命名について

データを調べていただきありがとうございます!私が使っている箇所をいくつか確認しましたが、シャドーイングを許可しても問題になりそうなものはありませんでした。私の場合、ポインタとして渡されることを期待するインターフェースを受け取り、それをリフレクトライブラリに渡す際に、パラメータの期待を「自己文書化」する方法として使うことが多いです。例えば:

func (r *R) Next(ptr interface{}) error {
  return r.jsonDecoder.Decode(ptr)
}

私の例の中で、ptr をこの「文書化目的」の方法で使いつつ、protobuf リテラルを初期化する必要があるケースは見つかりませんでした。提案されている ptr 関数を使うのはそういった場面だと思います。

つまり、長くなりましたが、この方向に進むのであれば ptr という名前で問題ありません 👍。

コメント 79

投稿者: JAicewizard | 投稿日: 2021-04-27

議論を完全には追えていませんが、関数を使うのは Go らしくないやり方だと本当に感じます。すでにポインタを作成する方法は new& の2つ(あるいはそれ以上)あります。これのさらに別の方法を追加すべきではないと思います。政治的には楽かもしれません(&int() vs &int{} の議論がない)し、実装も簡単です。しかし(私の意見では)Go のやり方にはフィットしません。

コメント 80

投稿者: davecheney | 投稿日: 2021-04-27

私は &int(n) の形式を好みましたが、この問題が gRPC のようなエンコーディングを使う際に発生することを考えると、より簡単な解決策は、それらのプロジェクトが new やコンパクトなリテラル初期化をサポートしない少数のプリミティブ型のために https://pkg.go.dev/go.uber.org/thriftrw/ptr のようなものを含めることでしょう。

コメント 81

投稿者: thejerf | 投稿日: 2021-04-28

@JAicewizard、ここでの根本的な問題は、「Go のやり方」が2つあり、それらがある程度矛盾していることです。

明白な方は Go の一般的なシンプルさで、これは式の結果が何であれそのポインタを提供するために必要なことを一貫して行う &PrettyMuchAnythingHere 構文が好まれることにつながります。

微妙な方は、表面的にはユーザーからすべてのアロケーションを隠す動的言語のように振る舞ういくつかの構文糖にもかかわらず、Go はアロケーションについて明示的であるということです。:= を宣言として読みやすい(特にすでにアロケートされた値を無視する点で)ですが、単なる宣言ではなくアロケーションです。

同様に、

for ... {
    var x int
}

を for ループ内の int 変数の「宣言」として見やすいですが、そうではなく、アロケーションです。

& が操作対象がアロケーションであるかどうかに関係なく、ほぼ何にでもポインタを取れるようにすることは、「何がアロケーションか」という問題をさらに曖昧にし複雑にします。

このスレッドでこれまで提案されたすべての技術的解決策はシンプルで、(このレベルの変更としては)一般的に影響が小さいことを考えると、ここでの本当の問題は哲学的なものです。Go はアロケーションについて現在のように明示的であり続けるべきか(すでに100%ではないが、完全に隠しているわけでもないことに留意して)、それとも一般的により簡単な言語であるためにアロケーションの詳細をさらに隠す方向に進むべきか?

その問いに答えが出るまで、ここでの技術的な議論は堂々巡りになるでしょう。技術的な問題はほぼ議論し尽くされているからです。

コメント 82

投稿者: JAicewizard | 投稿日: 2021-04-28

x := &int(55)x := &int{55} を許可しても、x := &struct{a:55} と同じだけのアロケーションが発生します(つまり、x := &struct{a:55} がアロケートしないなら、最初の2つもアロケートしません)。

後者はすでに許可されているので、最初の2つはアロケーションの明示性に関してあまり変化をもたらしませんよね?

すべてを & 可能にしようとするといくつかの問題が生じることは理解していますが、より長い行を書かせることにもなり、それも(おそらく暗黙的に)Go の原則に反するのではないかと思います。

また、何かをコピーしてアドレスを取る汎用関数が欲しいのであれば、それは new に関する元の提案と非常に似ています。唯一の本当の違いは名前の付け方です。

@thejerf 説明ありがとうございます!これらのスレッドはかなり長くなり、追いかけるのが難しくなることがあります。

コメント 83

投稿者: dolmen | 投稿日: 2021-05-11

#46105 で、より簡潔ではないものの、より幅広いユースケースに対応できる構文を提案しています。馴染みのある構文です(if/switch で使われる ShortVarDecl を思い浮かべてください):

go
p1 := (x := 3; &x)
p2 := (c := rune(10); &c)
p3 := (day := time.Tuesday; &day)
p4 := (unspec := Name("unspecified"); &unspec)

ptrTime := (t := time.Now(); &t)

コメント 84

投稿者: quenbyako | 投稿日: 2021-05-12

@dolmen p1 := (x := 3; &x)

うわっ 😧

ラムダ関数のように見えます。Go においてこれが曖昧さよりも有用になるとは思いません…

ちなみに、この issue に対してはやりすぎだと思います。例えば p3 := (day := time.Tuesday; &day) はすでに p3 := &time.Tuesday で置き換えられます。なので p3 := &Name("unspecified")ptrTime := &time.Now() のような書き方の方が、特に新しい gopher にとってはより明白に見えると思います。

コメント 85

投稿者: dolmen | 投稿日: 2021-06-09

@quenbyako p3 := &time.Tuesday は今日の時点では動作しません。私の例のリストは Rob の提案にある例から来ています。また &time.Now() はこの提案の対象外です。

コメント 86

投稿者: justjoeyuk | 投稿日: 2021-06-15

なぜこれほど複雑な議論が飛び交っているのでしょうか?コア言語にいくつかのユーティリティを組み込むだけではダメなのでしょうか?

pointers.String(mystring)
pointers.Int64(myint)
pointers.SomeOtherPrimitive(myotherprimitive)

ここでの主な問題は、基本的なプリミティブ型からポインタへの変換のために多くのライブラリが独自の関数を作っていることです。考えられるすべてのエッジケースを探究する前に、まずその問題を解決すべきではないでしょうか。このような基本的なユーティリティを作って言語に組み込めば、非常に多くの人気ライブラリにわたる膨大な重複が解消されます。

ステップ1:プリミティブ型用のユーティリティ関数を作成する ステップ2:new アプローチなどを使って複合型にも機能を拡張する

少なくとも最初のステップを行えば、現在多くのライブラリに蔓延している大きな不満を解消できます。再度 https://github.com/AlekSi/pointer/issues/8 を参照してください。

コメント 87

投稿者: icholy | 投稿日: 2021-06-15

@justjoeyuk https://www.youtube.com/watch?v=rFejpH_tAHM

コメント 88

投稿者: alnr | 投稿日: 2021-06-16

以下の曖昧さを解決する別の選択肢として

i := &3 // *int か *int64 か?

コンパイラが型を推論できる場合にのみ構文を許可するという方法があります:

var p *int32 = &3 // OK

func f(p *int64) {}
f(&58123) // OK

type S struct { p *int }
s := S{ p: &999 } // OK

x := &4412 // エラー:x の型を推論できません

var tooSmall *int8 = &1024 // エラー:リテラル 1024 は int8 をオーバーフローします

コメント 89

投稿者: JAicewizard | 投稿日: 2021-06-16

その曖昧さは以前から存在していました:

var x = 5 // x の型は何?
fmt.Println(reflect.TypeOf(x)) // int です!!!

そのような明示的な型指定子の要件を追加するのは、これだけでなくもっと広い範囲をカバーする別の提案にすべきだと思います。そうすれば一貫性が保たれます。ポインタ付き int には必要で、他の int には不要というのではなく。

コメント 90

投稿者: clausecker | 投稿日: 2021-06-16

@alnr @JAicewizard foo が型なし整数定数の場合、&foo の型を int に固定することに特に問題は感じません。これは短い変数宣言 x := foo と一致しており、必要であれば常に明示的な型を指定できます。

コメント 91

投稿者: deanveloper | 投稿日: 2021-06-16

&5 を常に *int に割り当てると、var x int64 = &5 ができなくなります。また、&5 が状況によって異なる意味を持つのはよくないかもしれません。

現在、x := 5 ではこの問題に遭遇しません。5 自体は型なし整数定数であり、x := 5 では x に指定された型がないため、型なし整数定数のデフォルト型である int が割り当てられます。

&5 の場合はこれがはるかに複雑になります。「型なし整数定数へのポインタ」という概念がないからです。では x := &5&5 は何を意味するのでしょうか?x := &5var x *int64 = &5 の両方で機能する方法でどう解決するのでしょうか?

@clausecker が提供した解決策は x := &5 には機能しますが、var x *int64 = &5 ではコンパイルに失敗します。

コメント 92

投稿者: clausecker | 投稿日: 2021-06-16

@deanveloper 64ビット int の場合はキャストを追加する必要があります:x := &int64(5)。これは不合理だとは思いません。

コメント 93

投稿者: deanveloper | 投稿日: 2021-06-16

@clausecker それは動くと思いますが、特に初心者にとっては混乱するかもしれません。f(5) が動くのに、なぜ fPtr(&5) はダメなのか?(関数がそれぞれ int64 と *int64 を受け取る場合)

コメント 94

投稿者: clausecker | 投稿日: 2021-06-16

@deanveloper これが理想的ではないことは認めますが、この問題が頻繁に発生するとは思いませんし、特に初心者にとってはなおさらです。レアケースのために最適化する意味はありません。

ちなみに、これに対する私の提案は、スカラーの複合リテラルを構築できるようにし、それらを初期化子とは異なる左辺値にすることです。つまり、使いたい型に関係なく fPtr(&int64{5}) のように書く必要があります。大きな利点の一つは、この構文には暗黙的な型キャストが含まれないため、偶発的な型の混同に対してより安全であることです。

コメント 95

投稿者: deanveloper | 投稿日: 2021-06-16

個人的にはあまり賛成ではありません。int64 は複合型ではないので、複合リテラル構文を使うのは意味的にしっくりきません。しかし、その魅力は理解できますし、それ自体に反対というわけではありません。ただし、int64{…} が何を意味するかというセマンティクスの問題が出てきます。int64 は複合型ではないからです。例えば、durtime.Duration の場合、int64{dur} はできるのでしょうか?型変換とは異なるが全く同じではない、まったく新しい概念が追加されることになり、Go では避けたいものです。個人的には、型付き定数へのポインタを許可すること(x := &int64(5) のように)が最良の解決策だと思います。

コメント 96

投稿者: quenbyako | 投稿日: 2021-06-18

@justjoeyuk ユーティリティを組み込むだけではダメなのか

いいえ、できません。もし世界中のすべての問題をそれぞれ個別のライブラリを作って解決したいのであれば、Lua を使うことをお勧めします。Lua では標準ライブラリが5つ(記憶が正しければ)のパッケージで構成されています。しかも Lua をベースにすれば Go と同じことがすべてできます(はい、本当にすべて)。

問題を正しく理解してください:ポイントは「これで短く書ける」ということではなく、文字通りあらゆる2つ目のパッケージがシンプルな型へのポインタを作成する問題に直面しているということです。したがって、「これは問題ではない、pointers パッケージを使えばいい」と言うのは問題の解決策ではありません。


@alnr // *int か *int64 か?

え?もちろん *int になります。明示的な型アサーションのないすべての数値は int になるからです。間違っていますか?


@deanveloper おっしゃりたいことは理解できますが、言語パーサーをアップグレードすることはそれほど悪いアイデアではないと思います…正直なところよくわかりませんが、言語仕様に変更がなければあなたが正しいように見えます。

コメント 97

投稿者: DmitriyMV | 投稿日: 2021-06-19

いいえ、できません。

はい、できます。ジェネリックの pointer.Of(t T) *T がこれをかなりうまく解決します。

明示的な型アサーションのないすべての数値は int になる

厳密にはそうではありません。10.0 は float64 になります。ポインタ式には 型なし定数へのポインタ のような言語仕様の調整も必要になり、それはまた別の厄介な問題です。

コメント 98

投稿者: quenbyako | 投稿日: 2021-06-19

@DmitriyMV 私のコメントの趣旨を理解していただけていません:

ジェネリックの pointer.Of(t T) *T がこれをかなりうまく解決します。

いいえ、「なぜできないか」のポイントは技術的に今不可能だということではありません(もちろん可能です、pointers パッケージがあるので使ってください)。ポイントは文字通りあらゆる Go プロジェクトで最も頻繁に使われるクラッチの一つを避け、言語仕様を改善することです。はい、pointers パッケージの使用はクラッチです。なぜか構造体はすぐに初期化できるのに、シンプルな型は突然できないのです。

10.0 は float64 になる

その通りですが、やはり私のコメントの趣旨を理解していただけていません:

go
i := 123             // reflect.TypeOf(i) == "int"
var j = 123          // reflect.TypeOf(j) == "int"
var k int64 = 123    // reflect.TypeOf(k) == "int64"
ip := &123           // この提案では:reflect.TypeOf(i) == "*int"
var jp = &123        // この提案では:reflect.TypeOf(i) == "int"
var kp &int64 = &123 // この提案では、想像通り:reflect.TypeOf(k) == "int64"

f := 10.0             // float64
var g float32 = 10    // float32
fip := &10            // *int
ffp := &10.0          // *float64
var gp *float32 = &10 // *float32

コメント 99

投稿者: deanveloper | 投稿日: 2021-06-19

はい、できます。ジェネリックの pointer.Of(t T) *T がこれをかなりうまく解決します。

これはあまり良い解決策ではありません。(記憶が正しければ)戻り値の型は推論できないからです。つまり f(&x)(x が定数で &x が *int64 になる)の問題を本当には解決しません。型を指定する必要があります(つまり f(pointer.Of(int64(x)))f(pointer.Of[int64](x)))。これでは冗長性を減らすためにジェネリクスを使う意味がなくなります。

いずれにせよ、言語が複合リテラルへのポインタを許可しているので、型付き定数へのポインタも許可するのが理にかなっているように思えます(つまり &int64(x))。

型付き定数へのポインタを許可する際の唯一の懸念は次のようなケースです: const x int = 5; (…) &x

これは x へのポインタのように見えますが、実際には新しいポインタです。少し紛らわしいですが、良いアイデアだと思います。

コメント 100

投稿者: DmitriyMV | 投稿日: 2021-06-19

@quenbyako

はい、pointers パッケージの使用はクラッチです

このようなものを使えば:

package main

import (
	"fmt"
	. "pointers"
)


func main() {
	fmt.Println(PtrOf(10))
	fmt.Println(PtrOf(10.0))
}

クラッチではありません。あるいはエイリアスを使うこともできます。

なぜか構造体はすぐに初期化できるのに、シンプルな型は突然できない

  1. 構造体は複合型です。
  2. 構造体のアドレス取得は完全な型推論を行いません。つまり、var l = {Field1: 0} とは書けません。左側か右側に完全に指定された型が必要です。

私のコメントの趣旨を理解していただけていません

あなたも私の趣旨を理解していません。「型なし定数へのポインタの型」を形式的な用語で記述してみてください(これは仕様変更の話です)。

@deanveloper

(記憶が正しければ)戻り値の型は推論できない

できます:https://go2goplay.golang.org/p/SDLKf5rzosf

型付き定数へのポインタを許可するのが理にかなっている(つまり &int64(x))

それは合理的に聞こえます。new(int, 3) と同様ですが、new([]int, 3) に非常に似ています。ただし、i := &3 のようなものには強く反対します。


コメント 101

投稿者: deanveloper | 投稿日: 2021-06-19

可能です: https://go2goplay.golang.org/p/SDLKf5rzosf

それは私が意図したことの誤った使い方です。その場合、Pointerinterface{} を返しています。より正確な例は https://go2goplay.golang.org/p/bIGdW1JDioN で、ここでは Pointerint64 を返しています。

コメント 102

投稿者: DmitriyMV | 投稿日: 2021-06-20

より正確な例は https://go2goplay.golang.org/p/bIGdW1JDioN で、Pointer が int64 を返しています。

これは「使用箇所からの戻り値型の推論」に帰着すると思いますが、現時点ではそれは計画されていません。ただ、もしかするとそれは良いことかもしれません。なぜなら、その場合 p := Pointer(3) は本質的に p := &3 であり、p := &int(3) ではないからです。

コメント 103

投稿者: deanveloper | 投稿日: 2021-06-20

使用箇所からの戻り値型の推論に帰着すると思いますが、現時点ではそれは計画されていません

はい、それが私の言いたかったことです。

ただ、もしかするとそれは良いことかもしれません。なぜなら、その場合 p := Pointer(3) は本質的に p := &3 であり、p := &int(3) ではないからです。

私の主張は本質的に、ジェネリック関数を使うのは少し不格好であり、そもそも言語に備わっているべき機能の代替手段だということです(複合リテラルのインラインポインタを作成できるのに、他の式に対してそれができないのは奇妙に思えます)。

コメント 104

投稿者: quenbyako | 投稿日: 2021-06-22

@DmitriyMV . "pointers"

お願いですから、絶対にこれをしないでください。お願いします(すべてのGopherの皆さんへ)、やめてください。

ところで、new(int, 3) のようなものは、以前の代替案ほど恐ろしくは見えないので、良い設計提案になるかもしれません。

コメント 105

投稿者: benhoyt | 投稿日: 2021-09-23

これは最後に提案レビュー会議で5月5日に議論されたようです。明確なコンセンサスはありませんが、いくつかの良い選択肢があります。Russのシンプルな new(1) 形式に対してかなりの支持があり、Roger Peppeの ptr(1) という提案のような新しい組み込みジェネリック関数にも相当な支持があるようです。私の投票は ptr(1) です。「通常の」ジェネリクスを使うだけなので。ただし new(1) も気に入っています。レビュー会議で再度議論していただけないでしょうか?

コメント 106

投稿者: seebs | 投稿日: 2021-09-26

当初は &expr のようなものに賛成でしたが、よく考えると、一般的なケースでメモリ割り当てが発生するのは少し気に入りません。特に曖昧さが生じるためです:m[k] がある場合、p := &m[k]v := m[k]; p := &v と同等になるのでしょうか?そうだとすると、現在重大なエラーを防いでいるものが失われるように思えます。

一方で、単純型に対して複合リテラルを許可することには特に反対しません。つまり、int(3) は値3を持つ int 型の式でありアドレス取得不可ですが、&int{3} は初期化子3を持つint型のオブジェクトを宣言し、そのアドレスを取得することになります。これは &foo と一貫しているように思えます。

そして…式に対して自動的にオブジェクトを生成するのはおそらく悪い考えだという前提を受け入れるなら、式と波括弧形式のリテラルの間に区別を設ける良いケースがあると思います。後者は単に「この値」ではなく「これらのプロパティを持つオブジェクト」という概念を表現しているからです。

そして、これは一般的に関数の戻り値やマップのルックアップなどのアドレスを取得することを許可しないことの論拠だと思います。&m[k] が動作しないことは有益だと思うからです。m[k]の実際の現在のアドレスを返すことができない以上、代わりに、要求した時点のm[k]に偶然似ているオブジェクトのアドレスを返すべきではありません。

しかし &vtype{m[k]} と書く必要があれば、m[k]のアドレスを取得しているのではなく、無名リテラルのアドレスを取得していることを明確に示すことになります。

new(1) も反対はしませんが、波括弧リテラル構文の方がより良いと思いますし、いくつかの追加の型に対してそれができるようになることは良い変更だと思います。

コメント 107

投稿者: earthboundkid | 投稿日: 2021-09-26

Go 1.18が出れば、誰でも自分で func NewOf[T any](v T) *T を書けるようになります。まずは皆にそれを使ってもらい、広く楽しまれ受け入れられたら、1.19で組み込みの newof を追加すればよいと思います。

コメント 108

投稿者: moonchant12 | 投稿日: 2022-01-19

この提案が不整合を解消するためのものであるなら、関数のアドレス取得も許可すべきではないでしょうか?

つまり、

Go
fp := &func(){}

コメント 109

投稿者: earthboundkid | 投稿日: 2022-01-20

欲しい方のために new.Of() はこちらです: https://github.com/carlmjohnson/new

コメント 110

投稿者: rogpeppe | 投稿日: 2022-01-20

欲しい方のために new.Of() はこちらです: https://github.com/carlmjohnson/new

個人的には、このパッケージは「少しのコピーは少しの依存関係より良い」という格言の素晴らしい実例だと思います。

参考までに、私は必要な場所でこのコードをそのまま書いています:

func ref[T any](x T) *T {
    return &x
}

コメント 111

投稿者: earthboundkid | 投稿日: 2022-01-20

個人的には、このパッケージは「少しのコピーは少しの依存関係より良い」という格言の素晴らしい実例だと思います。

まあ確かに、でも人気が出たら「carlmjohnsonに5ドル送ってね!」と表示するバージョンに置き換える計画はどうなるんですか?

もっと真面目な話、ref も関数の良い名前ですね。

コメント 112

投稿者: acehow | 投稿日: 2022-04-20

#9097 から既に7年が経過しました。おそらくGoチームはこの変更をサポートするのにさらに7年かかるでしょう。

コメント 113

投稿者: quenbyako | 投稿日: 2022-04-21

@acehow 嘲笑する代わりに、実装を手伝うこともできます。これは見た目ほど簡単なタスクではなく、シンタックスシュガーを追加することはできますが、非常に遅くなってしまいます。

文字通り無料で超強力な言語を手にしているのに、メンテナーを嘲笑するのはあまり賢明ではありません。

また、この問題にはすでに良い解決策がありますので、このようなケースに対処するために新しいGoバージョンを待つ必要はありません。

コメント 114

投稿者: afa7789 | 投稿日: 2022-07-22

go
package types

func Ptr[T any](v T) *T {
 return &v
}

手軽に参照ポインタを作りたい時、types.Ptr( VARIABLE ) を使わなければなりません。これはGo自体にネイティブなキャストツールとしてあるべきだと思います。シンプルで便利などです。

len( VARIABLE )print( stuff) があるように、pointer( VARIABLE )ptr( VARIABLE ) があってもいいのではないでしょうか。

ちなみに、私も &[]string{VARIABLE}[0] を使ってポインタに素早くアクセスしていましたが、これは便利なものだと思います。

別の関数で使う必要がある時に素早く取得できる本当に良い方法です。

example.FunctionReceivePoitnerAndString(types.Ptr(Something),string1) のようにすれば、スコープの外でSomethingのポインタを作成する必要がありません。

編集:「今見たら皆さん同じことを提案していますね。全部読むべきでした、すみません」

コメント 115

投稿者: afa7789 | 投稿日: 2022-07-22

Go 1.18が出れば、誰でも自分で func NewOf[T any](v T) *T を書けるようになります。まずは皆にそれを使ってもらい、広く楽しまれ受け入れられたら、1.19で組み込みの newof を追加すればよいと思います。

素晴らしい。

コメント 116

投稿者: earthboundkid | 投稿日: 2022-07-22

これが独立して再発明されているというのは有用なデータポイントだと思います。 😃

コメント 117

投稿者: EraYaN | 投稿日: 2023-03-14

K8sはかなり前から k8s.io/utils/pointer パッケージでポインタユーティリティを提供しています。非常に頻繁に必要になるからです。そろそろ言語自体に取り込むことを検討する価値があるかもしれません。

コメント 118

投稿者: DeedleFake | 投稿日: 2023-03-14

@EraYaN

標準ライブラリにそのようなものを入れるのは、ジェネリクス導入後では明らかにやりすぎに感じます。以前のコメントで述べられているように、今必要なのは

go
func ptr[T any](v T) *T { return &v }

をどこかに一度定義するだけで、それらのポインタ変換関数のすべてを単一の関数定義でカバーできます。

コメント 119

投稿者: EraYaN | 投稿日: 2023-03-15

でも、そのようなシンプルなものこそ標準ライブラリに入れるべきではないでしょうか?Golangの標準ライブラリに対する姿勢が少し変わっていることは理解していますが、それでもコア機能のように思えます。

コメント 120

投稿者: itroot | 投稿日: 2023-03-15

でも、そのようなシンプルなものこそ標準ライブラリに入れるべきではないでしょうか?Golangの標準ライブラリに対する姿勢が少し変わっていることは理解していますが、それでもコア機能のように思えます。

同様の関数を持つライブラリはかなり多くあります。例えば: https://github.com/samber/lo#toptr

コメント 121

投稿者: ivalue2333 | 投稿日: 2023-04-03

でも、そのようなシンプルなものこそ標準ライブラリに入れるべきではないでしょうか?Golangの標準ライブラリに対する姿勢が少し変わっていることは理解していますが、それでもコア機能のように思えます。

標準ライブラリに追加されれば助かります。私たちのプロジェクトではptrの使用箇所が非常に多いです。このissueが活発に維持され、解決されることを願っています。

コメント 122

投稿者: ianlancetaylor | 投稿日: 2023-06-07

現時点で最も実現可能な選択肢は以下のようです:

  • 標準ライブラリのどこかに新しいジェネリック関数 func PtrTo[T any](v T) *T { return &v }
  • &T(v) で型 T の新しく割り当てられた変数のアドレスを値 v で取得
  • new(v)PtrTo(v) のように動作(あるいは new(T, v) かもしれないし、両方の形式を許可するかもしれない)

他にもいくつかのアイデアが挙げられましたが、これらより明らかに劣るものばかりです。

@griesemer、@bradfitz、そして @ianlancetaylor は new(v)new(T, v) の両方を許可することを好みます。後者の形式を許可することは、var x = vvar x T = v の両方を許可する方法と似ています。プログラマに明確なコードを書くことを委ねるものです。(いずれにせよ、もう一つの可能性として var x = T(v)new(T(v)) がありますが、少し冗長です)。

PtrTo の欠点は単に名前の問題です:標準ライブラリのどこにその関数を置くのか?&T(v) の欠点は複合リテラルとの類似性と、& がいつメモリ割り当てを行い、いつ既存の変数のアドレスを取得するのかについての混乱の可能性です。

ここでの変更について直近の計画はありませんが、new(v)new(T, V) という選択に対して強い反対意見はありますか?よろしくお願いします。

コメント 123

投稿者: benhoyt | 投稿日: 2023-06-08

@ianlancetaylor この件に戻ってきてくださりありがとうございます!「その選択に対して強い反対意見はありますか」の3つの選択肢のうちどれを指しているか明確にしていただけますか?現在少し曖昧です。

&T(v) よりも new(v) を少し好みます。new(time.Now()) のようなケースで冗長な繰り返しがなくなるからです。もう一方の構文だと &time.Time(time.Now()) になってしまいます。明確さが必要な場合に new(T, v)追加でサポートされるのは問題ありません。new() は常に「新しい」ものを作成することがより明確です。

ptr.PtrTo のもう一つの欠点は、importが必要なこと(および各使用箇所でパッケージのプレフィックスが必要なこと)です。大きな問題ではありませんが、new が常に利用可能で非常に短いことに比べると、わずかな煩わしさがあります。

コメント 124

投稿者: griesemer | 投稿日: 2023-06-08

@benhoyt Ianが意図したのは3つの最も実現可能な選択肢の最後のものです:new(V)new(T, v) に対して強い反対意見はあるか、ということです。これらはそれぞれ PtrTo(v) (または PtrTo[T](v) )のように動作します。

コメント 125

投稿者: ianlancetaylor | 投稿日: 2023-06-08

明確になるようコメントを編集しました。ありがとうございます。


コメント 126

投稿者: willfaught | 投稿日: 2023-06-08

p := &3

しかし、3には型がないという厄介な問題があり、単純にうまくいきません。

3には型があり得ます:整数リテラルのデフォルト型であるintです。3.0はfloat64、"3"はstringなどです。

代わりに、型変換(おそらく型アサーションも、ただしここではそれは気にしないことにしましょう)がアドレス取得可能であると定義します。

アドレス取得可能であるかのように使用されている場合、任意の式をアドレス取得可能にしてはどうでしょうか?

もちろん、それは大して追加にならず、冗長な記述は煩わしいのですが、以下の形式を可能にし、以前は不格好だった多くのポインタ構築を簡単にします:

p1 := new(int, 3)

ここで型引数が必要な理由がわかりません。リテラルであればデフォルト型を使用します。そうでなければ実際の型を使用します。new(3)new(int(3))を意味し得ます。new(f())はfの戻り値の型を使用できます。

型変換をアドレス取得可能にすることを特別なケースとするのは一貫性がなく、魔法的です。そこに韻も理由もありません。これは新しいGoユーザーが暗記しなければならない魔法の呪文のひとつとなり、このGitHub issueの難解な知識を借りてなぜそのような形で存在するのかを説明することになるでしょう。アドレス取得可能性をこの問題に組み込むのであれば、このissueが目指していることを達成できるよう、アドレス取得可能性をより一般化・表現力のあるものにすることに注力すべきだと思います。そうでなければ、アドレス取得可能性はまったく関与させるべきではありません。

コメント 127

投稿者: DeedleFake | 投稿日: 2023-06-08

@benhoyt

new(time.Now())は私にはとても奇妙に読めます。&time.Now()の方がはるかに好みです。

詳しく書こうとしましたが、すぐ上で@wilfaughtに先を越されました。私の考え方は、

go
return &Struct{}

は概念的に以下と同等である、というものです

go
s := Struct{}
return &s

であれば、なぜこれを_すべての_単一値の式に拡張しないのでしょうか?構造体、配列、スライス、マップ型がその点で特別であることは常に私を混乱させてきました。中間変数の省略を何でも許可すればいいのです。&3でも、&"example"でも、&f()でも。

余談:へぇ、マップはポインタ初期化できるんですね。&map[int]int{1: 3, 2: 2, 3: 5}は完全に有効です。これを書くまで知りませんでした。

コメント 128

投稿者: benhoyt | 投稿日: 2023-06-08

@willfaught Ianの最近の質問ではなく、冒頭の元の提案に対して返信しているようですね。それは構いませんが、いくつかの論点はすでに対処されています。特に、new(int, 3)について「ここで型引数が必要な理由がわかりません」と書かれていますが、Ianの提案はnew(3)を(new(int, 3)に加えて)許可するというものです。

@DeedleFake 実は以前、&任意の式構文を許可する提案としてissue 22647を作成しましたが、それには反対するよう説得されました。上記やIanのissue 9097へのコメントで指摘されているように、これは完全には「概念的に同等」ではありません。&Struct{}を行うと、Goは毎回新しい値を作成してそのアドレスを返しますが、&sを行うとGoは毎回その同じ変数のアドレスを返します。また、&myMap[k]と書けるようになりますが、これはマップのエントリがアドレス取得可能であるかのように見えますが、実際にはそうではありません(その構文は現在エラーになります)。そのため、&任意の式を許可することは、その価値に見合わないほど混乱を招くと思います。

コメント 129

投稿者: zephyrtronium | 投稿日: 2023-06-08

提案されているnew(v)は、vが型なしリテラルで、式が定義型を必要とするコンテキストで使用される場合、正しい型を推論しますか?つまり、以下は型チェックを通過しますか?

go
type MyString string

func F(*MyString) {}

func main() {
	F(new(""))
}

コメント 130

投稿者: willfaught | 投稿日: 2023-06-08

特に、new(int, 3)について「ここで型引数が必要な理由がわかりません」と書かれていますが、Ianの提案はnew(3)を(new(int, 3)に加えて)許可するというものです。

@benhoyt わかっています、私の異議はnew(int, 3)を含めることに対するもので、型引数が冗長だからです。new(3)は問題ありません。var t T = xが許可されている理由の一部は、代入に変換・代入可能性が関わる可能性があるからです。もう一つの理由はドキュメンテーションです。割り当てのための式にはそのような必要性はありません。

実際、彼の提案はnew(v)であってnew(3)ではないので、デフォルト型に関する私の指摘はまだ有効だと思います。

コメント 131

投稿者: ianlancetaylor | 投稿日: 2023-06-08

new(3)new("")のような場合、型なし定数にはvar x1 = 3var x2 = ""と書いた場合と同様にデフォルト型が与えられます。

F(new(""))についての質問は興味深いものです。現在これは型チェックを通過しません。推論された型を関数呼び出しの中に持ち込むことはなく、関数呼び出しから外に持ち出すだけだからです。これは以下と似ています

Go
type MyString string

func F(*MyString) {}

func PtrTo[T any](v T) *T { return &v }

func main() {
    F(PtrTo(""))
}

現在、これは以下のエラーでコンパイルに失敗します

foo.go:10:7: cannot use PtrTo("") (value of type *string) as *MyString value in argument to F

原理的には、Fの呼び出しを満たすためにこれはPtrTo[MyString]でなければならず、したがって型なし文字列はMyStringとして扱われるべきだと推論することは可能です。しかし、現在そのような型推論は行っておらず、行う予定もありません。

コメント 132

投稿者: rogpeppe | 投稿日: 2023-06-08

new(v)に対する私の主な懸念は、私の知る限り、すべての組み込み関数は特定の引数に対して型_または_値の_いずれか_を取るということです。つまり、特定のプリミティブに対して、二つのうちどちらを期待しているかが常にわかります。

new(v)はその境界を曖昧にします。

型の式と値の式は同じ名前空間に存在するため、原理的にはこれを行うことに問題はありませんが、不安を感じます。

また、ツーリングの能力がやや低下します。new(と入力したとき、エディタが型を書こうとしていることを認識できなくなり、適切な候補を表示できなくなるからです。

個人的には、この引数の種類の曖昧さを回避する新しい組み込み関数(refptrnewof...?)を支持します。

コメント 133

投稿者: robpike | 投稿日: 2023-06-08

この問題は、型を必ず指定することを要求すれば回避できます。それが元の提案でした。この機能は比較的まれにしか使われないと思いますし、型を指定する要件は一貫性を維持しつつ全体的にほとんど負担を増やしません。

コメント 134

投稿者: leighmcculloch | 投稿日: 2023-06-12

この時点で最も実行可能な選択肢は以下のようです:

説明されている3つの選択肢は、私には実行可能に思えません:


  • 標準ライブラリのどこかに新しいジェネリック関数 func PtrTo[T any](v T) *T { return &v }

この選択肢を実験してみると、コピーして割り当てポインタを返すジェネリック関数は、他のどの選択肢よりも望ましくないようです。なぜなら、値が常にヒープに割り当てられるのに対し、議論されている他の選択肢ではコンテキストに応じてスタックまたはヒープのいずれかに値が配置されるからです。(他の提案がnew&S{...}の既存のアロケーション動作に従うと仮定した場合。)


  • &T(v) で型Tの新しく割り当てられた変数の値vでのアドレスを取得する

&T(v)を分解して右から左に読むと:

  1. v ある値が与えられる
  2. T(...) 値をTに変換する
  3. & アドレスを取得する

パート2は意外に思えます。vがすでに型Tである場合、変換は不要に見えます。&はすでに「アドレスを取得する」を意味します。T(...)はすでに「値を型Tに変換する」を意味します。この二つを組み合わせると新しい意味:「Tの新しい値を一時変数に作成してそのアドレスを取得する」になるのは意外で直感的ではありません。元の提案では型変換は常に新しい値を作成すると強調していますが、これは直感的ではありません。型変換を見たとき、新しい値を作成する意図ではなく、変換する意図のみを見るので、&T(v)は意図を十分に明確に伝えないと思います。


new(v)new(T, V)の選択に対して強い反対はありますか?

new(v)は好みではありません。なぜならnew関数の最初のパラメータが値にも型にもなるからです。関数のオーバーロードがGoでサポートされていないことを考えると、これは意外に思えます。

new(T, v)のみを追加することも好みではありません。冗長で、構造体で使用する際に型の冗長な繰り返しを導入するからです。この問題にはすでに冗長な解決策があり(例:var v = 3; var p = &v;)、この提案は結果が明確で簡潔であればより大きな成果となります。

new(T, v)のみを追加することが好みでないもう一つの理由は、私が見るGoのアプリケーションコードの多くがnewよりも&S{...}を好むからです。同じことを行う新しい方法を作成し、その新しい方法がより冗長である場合、新しい開発者がGoを学ぶのが難しくなります。new(T, v)&S{...}が機能するのに&3が機能しないという新しい開発者が経験する混乱に対処しません。

@robpikeの元の提案には、&vnew(T, v)と同等となる省略形が含まれていました。その省略形がなければnew(T, v)はかなり魅力がないように思えます:

つまり、

p := &expression

exprが既存のメモリ位置ではない場合、以下の省略形として定義されます

p := new(typeOfExpression, expression)

コメントで述べられた&expressionの欠点があるにせよ、新しいGo開発者に説明するのが最も簡単に思えます。&vが、式がアドレス取得可能でない場合(定数や関数の戻り値など)に一時変数を作成してその一時変数のアドレスを取得すると説明する方が簡単だと思います。&省略形が許可されるケースは、最も混乱を招くケース、例えば&myMap[k]でマップにアクセスすることを禁止するように制限できます。

new(T, v)を追加する場合、定数と関数戻り値のアドレス取得不可能なケースのvに対して、省略形&vも追加するのが理想的です。&3&f()が一時変数・新しい値のアドレスであることはかなり明確です。

コメント 135

投稿者: earthboundkid | 投稿日: 2023-06-12

この選択肢を実験してみると、コピーして割り当てポインタを返すジェネリック関数は、他のどの選択肢よりも望ましくないようです。なぜなら、値が常にヒープに割り当てられるのに対し、議論されている他の選択肢ではコンテキストに応じてスタックまたはヒープのいずれかに値が配置されるからです。

これはジェネリクスが統合される過程での一時的なコンパイラの不具合だと思います。これに基づいて長期的な決定を下すべきではないでしょう。

コメント 136

投稿者: DmitriyMV | 投稿日: 2023-06-12

@leighmcculloch

私の結果(1.20.5時点)は以下の通りです:

Benchmark1
Benchmark1-10     	1000000000	         0.9498 ns/op	       0 B/op	       0 allocs/op
Benchmark2a
Benchmark2a-10    	1000000000	         0.9457 ns/op	       0 B/op	       0 allocs/op
Benchmark2b
Benchmark2b-10    	1000000000	         0.9585 ns/op	       0 B/op	       0 allocs/op
Benchmark3
Benchmark3-10     	1000000000	         0.9427 ns/op	       0 B/op	       0 allocs/op
Benchmark4
Benchmark4-10     	1000000000	         0.9438 ns/op	       0 B/op	       0 allocs/op

アロケーションはありません。

コメント 137

投稿者: ianlancetaylor | 投稿日: 2023-06-12

個人的には、&2&vvが変数の場合)の間の非常に異なる動作は、&2を言語に追加すべきではないことを意味すると思います。(複合リテラルの構文は&vと十分に異なっているため、混乱は起こりにくいでしょう。)

コメント 138

投稿者: leighmcculloch | 投稿日: 2023-06-12

&2&vvが変数の場合)の間の非常に異なる動作は、&2を言語に追加すべきではないことを意味します。(複合リテラルの構文は&vと十分に異なっているため、混乱は起こりにくいでしょう。)

Goユーザーの視点からすると、定数と関数に対する基盤となる動作の違いは、開発者が驚くような形でプログラムの動作の違いにはならないと思います。

@ianlancetaylor &2がアプリケーションの動作として混乱を招く例はありますか?

定数の場合、開発者が受け取ることを期待できるアドレスはありません。&2は常に新しいアドレスを返しますが、これは&vとは異なります。しかし、&S{...}でこれに驚いたという苦情を見たことがないので、問題にはならないでしょう。(

関数の戻り値についても同じことが言えると思います。スレッドの上部で共有された例(https://github.com/golang/go/issues/45624#issuecomment-822426632)は、関数の戻り値がどのように混乱を招き得るかについてのものですが、そのコードサンプル自体がすでに非自明な概念の理解を必要とする混乱を招くものです。

マップについては混乱があることを理解していますが、マップ参照での使用(&myMap[...])は禁止できます。(マップにメソッドが追加されれば、&myMap.get(...)は使用可能で混乱も少ないでしょう。)

コメント 139

投稿者: DeedleFake | 投稿日: 2023-06-13

個人的には、&2&vvが変数の場合)の間の非常に異なる動作は、&2を言語に追加すべきではないことを意味すると思います。(複合リテラルの構文は&vと十分に異なっているため、混乱は起こりにくいでしょう。)

既存の複合リテラル構文と、関数の戻り値のアドレスを取得する新しい機能を除いて、すべての割り当てアドレス使用に追加の括弧を必要とするのはどうでしょうか?つまり&(3)&(someMap["key"])です。~~これは現在合法ではなく、~~私には十分に異なっていて明白に見えます。

編集:これは現在_合法_のようです。へぇ。いずれにせよ、差別化要因としてまだ機能するかもしれません。

コメント 140

投稿者: zephyrtronium | 投稿日: 2023-06-13

@DeedleFake これは確かに現在合法です:https://go.dev/play/p/m0YPHwq7CpR

コメント 141

投稿者: zephyrtronium | 投稿日: 2023-06-13

@leighmcculloch

Goユーザーの視点からすると、定数と関数に対する基盤となる動作の違いは、開発者が驚くような形でプログラムの動作の違いにはならないと思います。

既存のオブジェクトへのポインタを取得すること(必然的にエイリアスになる)と、新しいオブジェクトを割り当てること(必然的にエイリアスにならない)の違いは、確実に動作の違いであり、確実に観察しやすいものです。それが「驚くような形で」あるかどうかは、個々のプログラマーが何を期待するかに依存します。Go学習中のユーザーが&expr()はエイリアスにならないことに慣れ、その後&variable&slice[x]でエイリアスになった場合、驚くような形で異なることになります。

定数の場合、開発者が受け取ることを期待できるアドレスはありません。&2は常に新しいアドレスを返しますが、これは&vとは異なります。しかし、&S{...}でこれに驚いたという苦情を見たことがないので、問題にはならないでしょう。(

参考までに、このプログラムがfalseを出力することに私は実際に驚きます。少し変更すると、そうならなくなります:https://go.dev/play/p/UcHpIBuuoge 。Go仕様にはこう書かれています。「異なるゼロサイズ変数へのポインタは、等しい場合もあれば等しくない場合もある。」これに驚いた人に説明したことがあります。

マップについては混乱があることを理解していますが、マップ参照での使用(&myMap[...])は禁止できます。(マップにメソッドが追加されれば、&myMap.get(...)は使用可能で混乱も少ないでしょう。)

一つの特別なケース(複合リテラルのアドレスを取得できる機能)を別の特別なケース(マップのインデックス式のみが単項&のオペランドになれない)に置き換えることが、言語をより分かりやすくするとは思いません。より混乱を招かないかもしれませんが、「悪化しない」は言語変更の十分な基準ではないと思います。

混乱を招き得る式はこれだけではありません。型アサーションについてはすでに言及されています:&anyVar.(int)any内のintのアドレスを取得するのではなく、intの動的値のコピーを割り当てることになります。非ポインタの動的値に対してポインタメソッドを呼び出そうとして、インターフェース内の値のアドレスを取得できないことを説明したこともあります。「動作する」が実際にはコピーに対して操作することは、彼らにとって驚きだったかもしれません。

コメント 142

投稿者: ianlancetaylor | 投稿日: 2023-06-13

&2がアプリケーションの動作として混乱を招く例はありますか?

&syscall.ImplementsGetwd&syscall.ForkLockを比較してみてはいかがでしょうか?

コメント 143

投稿者: clausecker | 投稿日: 2023-06-13

単項+演算子のセマンティクスを任意の型のオペランドに適用できるように拡張し、「左辺値を右辺値に変換する」というセマンティクスを持たせましょう。そうすれば、&+fooは明確にfooのコピーを指します。

コメント 144

投稿者: ianlancetaylor | 投稿日: 2023-07-19

何人かの方々(@rogpeppe、@robpikeなど)が、new(T, v)new(v)の両方ではなく、new(T, v)のみにすべきだとコメントしています。これにより、newの最初の引数は常に型になります。

確かに、これにより一部のケースでは式がより冗長になります。しかし、長い型名が使用されるほとんどのケースは構造体であり、&S{}リテラル表記があると推測するのが妥当でしょう。単純なvと複雑なTnew(T, v)を書きたいケースはあまりないでしょう。少なくとも、そのようなケースが発生する具体的な例を見てみたいところです。

new(T, v)は構文的にmake(T, length)にも似ていますが、意味は異なります。

上記で議論されたように、&構文の使用は混乱を招く可能性がある理由がいくつかあります。new(T, v)構文はかなり明確で、混乱を招くことはないでしょう。

コメント 145

投稿者: rogpeppe | 投稿日: 2023-07-20

@ianlancetaylor 私のコメントについて少し誤った表現がされています。繰り返しますが、私の好みは依然として、値のみを取る関数に新しいスペリングを選ぶことです。機能をnewに無理やり詰め込むことには乗り気ではありません。

コメント 146

投稿者: Merovius | 投稿日: 2023-07-20

new(T, v)も特に好きではありません。makeとの類似性は魅力を高めるのではなく、むしろ損なうと思います:一部の型にはmakeが使用され、他の型にはnew(または&T{})が使用される理由について、すでにある程度の混乱があります。また、本当に当てはまるとも思いません。make(T, v)ではvnew(T, v)での意味とは完全に異なるものを意味します。最後に、私の意見では、型Tを書き出す追加のオーバーヘッドは多くのケースで大きいです。初期化したい値から型が推論できる場合はなおさらです。new(int64(42))new(int64, 42)と比べてタイプ量も読む量も変わりませんが、new(time.Second)new(time.Duration, time.Second)よりも大幅に良いです。型をそこに含めることが本当に何かを追加するとは思いません。定数リテラルから型を推論することにはすでにある程度慣れています。

とはいえ、new(T, v)は現状よりはまだ良いので、new(v)で合意できず、pointerTo(v)のより良い名前でも合意できないなら、new(T, v)で我慢します。

コメント 147

投稿者: icholy | 投稿日: 2023-07-20

しかしnew(time.Second)new(time.Duration, time.Second)よりも大幅に良いです。

time.Secondが値であることをすでに知っていればそのとおりです。しかし、new(pkg.Ident)を読む場合、pkg.Identの定義を確認しないとどちらのオーバーロードを使用しているか判別する方法がありません。

コメント 148

投稿者: rogpeppe | 投稿日: 2023-07-20

もう一つのポイント:new(T, v)形式はポインタ型のコピーを作りたいという珍しくないケースでも不便です。

例:

func f(x Foo) {
   x.field = new(int, *x.field)   // intはFoo.fieldの型
   // 共有ポインタを心配せずにx.fieldを使用する。
}

本来不要な型を記述する必要があり、その型が明らかでない場合もあるため、コードがやや脆くなります。

「pointerTo」スタイルの関数を使えば、私の意見ではより良くなります:

func f(x Foo) {
   x.field = ref(x.field)
   // 共有ポインタを心配せずにx.fieldを使用する。
}

// ...「ref」の何らかのスペリングで
func ref[T any](x T) *T { return &x }

コメント 149

投稿者: earthboundkid | 投稿日: 2023-07-20

new(v)形式は好みではありません。なぜなら、読み手がvが値なのか型なのかを知っている必要があるからです。new(T, v)とref(v)の間であれば、どちらでも受け入れられます。結局のところ、新しい事前宣言識別子を追加しない方が良いのか、既存の識別子にオーバーロード形式を追加しない方が良いのかという問題です。

コメント 150

投稿者: ianlancetaylor | 投稿日: 2023-07-20

@rogpeppe 誤った表現をしてしまい申し訳ありません。


コメント 151

投稿者: Merovius | 投稿日: 2023-07-20

個人的にも new(X) は曖昧だと思います。X が値なのか型なのかを知る必要があるからです。ただ、私がそれを持ち出したのは new(T, v) が好みではないという点を述べるためでした。私の一番好きなバージョンは ptr(v) ですが、ptr は許容される名前ではないと思います。誰かもっと良い名前を思いつくかもしれません。

しかし前にも述べたように、型引数なしバージョンのビルトインについてどの色のバイクシェッドにも合意できないのであれば、new(T, v) でも何もないよりはましです。

コメント 152

投稿者: DeedleFake | 投稿日: 2023-07-20

もし new(T, v) が採用される形式になった場合、結局あちこちで以下のように書くことになるでしょう。

go
func ptr[T any](v T) *T { return &v }

面倒を避けるために、とはいえ new(T, v) が便利な場面もあるので頻度は減るでしょう。全体的に、new() をオーバーロードするのは間違った方向だと思います。特に、大幅に使い勝手の悪い構文を強いることになるのであればなおさらです。

コメント 153

投稿者: willfaught | 投稿日: 2023-07-21

すでにある構文を活かす方向もありえます:

go
&&value
&&time.Second
var p *int = &&123

コメント 154

投稿者: jimmyfrasche | 投稿日: 2023-07-22

#34515 で議論されているサブプロポーザルの一つに、ジェネリック関数の呼び出しで型を省略できる場合と同様に make/new でも型を省略できるようにするというものがあります。new(Type, value) はその選択肢を潰してしまう可能性があります。結局 new(value) に戻ってしまうからです。

コメント 155

投稿者: metux | 投稿日: 2023-10-02

おまけの課題:make についても同じことをしてみてください 😃 結局のところ、構造体リテラルの初期化構文をなんらかの方法で拡張して、これら4つのことをカバーできるようにするということになると思います:

make() について:

まだ、mapフィールドがゼロ値からそのまま使えるのではなく、なぜ常に明示的に make() で初期化する必要があるのか理解できていません。これにより、構造体でmapを使うのがずっと複雑になります。

コメント 156

投稿者: DeedleFake | 投稿日: 2023-10-03

すべての型のゼロ値は文字通りメモリが全てゼロにセットされたものです。mapは内部的には構造体へのポインタなので、そのゼロ値は文字通りnilポインタです。ゼロ値を異なる動作にしようとすると、とりわけ以下のような状況で失敗します:

go
func addThing(m map[string]string) {
  // This would allocate an hmap, but only this m would get set to its address.
  m["example"] = "This is an example."
}

func main() {
  // Remember, this is a *hmap.
  var m map[string]string

  // addThing() gets a copy of the address, currently nil.
  addThing(m)
  // No matter what addThing() does, the local m is still nil at this point.
}

コメント 157

投稿者: metux | 投稿日: 2023-10-04

すべての型のゼロ値は文字通りメモリが全てゼロにセットされたものです。mapは内部的には構造体へのポインタなので、そのゼロ値は文字通りnilポインタです。ゼロ値を異なる動作にしようとすると、とりわけ以下のような状況で失敗します:

go
func addThing(m map[string]string) {
  // This would allocate an hmap, but only this m would get set to its address.
  m["example"] = "This is an example."
}

なるほど、つまりmapは参照やポインタではなく値として直接渡せる一方で、同じ基盤のハッシュテーブルを使い続けるということですね。嬉しいニュースです、本当にそうなのか確信がなかったので 😃

確かに、これではオンデマンド割り当てがこの種の問題を引き起こします。もしそちらの方向に進むなら、明確にドキュメント化する必要があり、おそらくコードチェッカーがプログラマーが忘れている可能性のあるバグを探すべきでしょう...確かに望ましくはありません。

しかし、別の明示的な make がない場合(おそらく異なる初期サイズを要求するもの)、暗黙的に makemap() 呼び出しを発行するようにしたら(コンパイラの複雑さ増加以外に)何が起こるでしょうか?覚えている限り、最悪の場合(気づかれない明示的な make がある場合)、無駄な割り当てやメモリクリアが発生する可能性がありますが、セマンティクスには全く影響しないはずです。

何か見落としていますか?

ところで、コード生成とランタイムコードが実際にどのように連携するのかまだ完全に理解していません...プログラムがmapを全く使わない場合、デッドコード除去がmap関連のランタイムコードをすべて排除するのでしょうか?

よろしくお願いします。

コメント 158

投稿者: adonovan | 投稿日: 2023-12-06

いくつかのコメントをまとめます:

  • &v は割り当てを行うのか、既存の変数のアドレスを返すのかが不明確です。
  • new(v) は簡潔ですが、new(T) と混同しやすいです。
  • new(T, v) は明確で make に似ていますが、型が冗長です。
  • この関数は1行で書けます:func addr[T any](v T) *T { return &v }

ヘルパー関数を書くのが簡単であることを考えると、言語変更は限界的な価値しか追加しません。 @robpike、ここで何かする必要があるとまだお考えですか?

コメント 159

投稿者: robpike | 投稿日: 2023-12-07

@adonovan 確かに 必要性 はありませんが、このアンバランスさは依然として気になります:複雑なものへのポインタを作る方が、単純なものよりも簡単だということです。

あなたのリストのどの項目も致命的には思えません。最初の項目は私が提案したものとは無関係で、中間の2つは正しいですが明らかに問題とは言えず、関数を書くのが簡単だということは根本的な非対称性には触れていません。

コメント 160

投稿者: findleyr | 投稿日: 2024-01-10

この議論がコンセンサスに至るまでオープンのままにしておきます。 — rfindley(言語プロポーザルレビューグループを代表して)

コメント 161

投稿者: perj | 投稿日: 2024-01-11

この関数は1行で書けます:func addr[T any](v T) *T

過去1年半を振り返ると、新しいパッケージで必要になるたびに、約2ヶ月に1回この関数を書いているようです。特にユニットテストファイルでの文字列へのポインタで、この必要性が生じることに気づきました。

確かに、API仕様から生成されたコードを多く扱っています。そのようなコードは nilと空文字列が等価であると確信できないため(そして実際に等価ではないので当然ですが)、*string を多用する傾向があります。

非常に煩わしいわけではありませんが、パッケージをこの関数で散らかしているような感じがするので、書かなくて済むなら歓迎です。インポートするパッケージに入れることもできますが、1行関数のためにそれもやりすぎに思えます。

コメント 162

投稿者: smasher164 | 投稿日: 2024-01-12

割り当てについて明示的であることに関して言えば、Goはすでにその段階を過ぎていると思います。オブジェクトがスタック上にあるかヒープ上にあるかは完全にエスケープ解析に依存しています。むしろ、&v はMLの ref v に近いもので、「ここにこの値へのミュータブルな参照があります — スタック上にあるかヒープ上にあるかは実装依存です」と言っているようなものです。

コメント 163

投稿者: andig | 投稿日: 2024-01-12

ヘルパー関数を書くのが簡単であることを考えると、言語変更は限界的な価値しか追加しません。

ヘルパー関数はポインタ値を引数に取って簡単に呼び出せてしまい、それは通常プログラミングエラーです。言語構文であれば、ルールによるか間接参照の層を取り除くことで、これを防ぐことができます。

コメント 164

投稿者: earthboundkid | 投稿日: 2024-07-26

  • new(T, v) は明確で make に似ていますが、型が冗長です。

型推論が十分にうまく機能して(おそらくこれをハードコーディングすることで)、new(T, T{ field1, field2 }) の代わりに new(T, { field1, field2 }) と書けるようになれば、それほど悪くないでしょう。カンマは奇妙ですが、Tとvを混同しないためにあるので良いことです。

コメント 165

投稿者: bbkane | 投稿日: 2025-02-20

これについて何か新しい進展はありましたか?

@perj と同様に、複数のモジュールでこの関数を書いてきましたし、サードパーティのモジュールでも多く見かけます。しかも名前がバラバラなことが多いです。

@robpike の「対称性」の議論に加えて、標準化を実現するために、プリミティブ型へのポインタを作成する標準的な方法が欲しいです。

このIssueは2021年からオープンされており、ほとんどのコメント投稿者はこのアイデアを歓迎していますが、勝利する構文についてどちら側にも非常に強い議論は見られていません。言語チームの誰かが勝者を選んでいただけませんか?そうすれば実装されたときに使い始められます。

コメント 166

投稿者: adonovan | 投稿日: 2025-02-20

@robpike のオリジナルのオプション1、new(T, v) には多くの利点があります。型が明示的で、新しい変数の割り当ても明示的なので、その意味を誤解することはほぼ不可能です。そして、既存の関数にパラメータを追加するだけなので、小さな言語変更です。

次のレビューで再度持ち出します。

コメント 167

投稿者: robpike | 投稿日: 2025-02-20

それを聞いてとても嬉しいです。この言語の欠点(非対称性)についてはとても気にかけていますが、重大な欠陥ではありません。

コメント 168

投稿者: mitar | 投稿日: 2025-02-20

私の主なユースケースはJSONのオプショナルフィールドで、*string*int 型を持つ構造体を使う場合です:

go
type Foo struct {
  String *string `json:"string,omitempty"`
  Int *int `json:"int,omitempty"`
}

このような構造体を作成するのは現在非常に面倒です。しかしオプション1の提案された構文もかなり冗長です:

go
Foo{
  String: new(string, "x"),
  Int: new(int, 0),
}

以下のように書けるようにできないでしょうか:

go
Foo{
  String: new("x"),
  Int: new(0),
}

コメント 169

投稿者: ianlancetaylor | 投稿日: 2025-02-20

@mitar このIssueでは多くの議論がありました。そのアイデアは https://github.com/golang/go/issues/45624#issuecomment-822211103 で最初に提案されたと思います。

コメント 170

投稿者: ianlancetaylor | 投稿日: 2025-02-20

このIssueについての私の最新のまとめは https://github.com/golang/go/issues/45624#issuecomment-1581546783 で、その後に https://github.com/golang/go/issues/45624#issuecomment-1642776089 があります。

コメント 171

投稿者: ianlancetaylor | 投稿日: 2025-03-05

通常のプロポーザル委員会に移管します。

コメント 172

投稿者: rogpeppe | 投稿日: 2025-03-06

@robpike のオリジナルのオプション1、new(T, v) には多くの利点があります。型が明示的で、新しい変数の割り当ても明示的なので、その意味を誤解することはほぼ不可能です。そして、既存の関数にパラメータを追加するだけなので、小さな言語変更です。

参考までに、私は必要な場所ではいまだにこの小さな関数を書いています:

func ref[T any](x T) *T {
    return &x
}

例えば、ref(someMap[x])new(SomeType, someMap[x]) に置き換えると、コードがより冗長になり、mapの値の型が変更された場合に更新が必要になるため、少し脆弱になり、差し引きマイナスです。

上記の関数を使うもう一つのよくあるイディオムは、ポインタ値のシャローコピーを作ることです:

    x := ref(*y)

ここでも y の型を明示的に指定する必要があるのは不必要に面倒です。特にインライン定義された構造体のような名前が長い型の場合はなおさらです。

上記の ref 関数に相当するものが言語に追加されれば良いと今でも思っています。

コメント 173

投稿者: t9t | 投稿日: 2025-07-07

命名に関連して、このスレッドではまだ言及されていないと思う点を追加したいと思います(全部読みましたが、見落としていないことを願います)。

新しい関数を追加するという提案がいくつかあります。例えばビルトインの refptr、あるいは標準ライブラリの pointer.Of などです。

次のような仮想的なスニペットを想像してください:

a := 10
p := ptr(a)
// または:
r := ref(a)

これを見たときの最初の印象は、ptr(a)ref(a)&a と等価、つまり変数 a のアドレスを取得する(ポインタや参照を得る)ということです。しかしそうではありません:&a は毎回同じアドレスを返しますが、ptr(a)/ref(a) はコピーを作り、そのコピーのアドレスを返します。

new(T, v) 構文は特定のケースで冗長になるためあまり気に入ってはいませんが、ここでの new という名前は好きです。なぜなら、既存のものを指す(参照する)のではなく、何か新しいものを作成するというまさにやっていることを表しているからです。

コメント 174

投稿者: earthboundkid | 投稿日: 2025-07-07

new(T, v) 構文は特定のケースで冗長になるためあまり気に入ってはいませんが、ここでの new という名前は好きです。なぜなら、既存のものを指す(参照する)のではなく、何か新しいものを作成するというまさにやっていることを表しているからです。

同様の理由で newof という名前が気に入っています。

コメント 175

投稿者: adonovan | 投稿日: 2025-07-07

new(T, v) 構文は特定のケースで冗長になるためあまり気に入ってはいませんが、ここでの new という名前は好きです。なぜなら、既存のものを指す(参照する)のではなく、何か新しいものを作成するというまさにやっていることを表しているからです。

同様の理由で newof という名前が気に入っています。

思うのですが、new の解釈を変更して、現在の形式 new(T) を受け入れつつ、new[T]()new[T](expr)new(expr) といった新しい形式も受け入れるようにすることは可能でしょうか?呼び出し形式 f(x) と変換形式 T(x) はパーサーでは区別できず、パーサーは f を式としてパースし型と項の区別は型チェッカーに委ねる必要があるため、型チェッカーが new の最初の引数を同様の方法で解釈することは可能なはずです。

そうすれば、ゼロ値の変数を構築する呼び出しには new(T) または new[T]() スタイルを使い、変数を初期化する必要がある場合には new(x) または new[T](x) を使えます。後者は x が T ではない場合のためです。


コメント 176

投稿者: apparentlymart | 投稿日: 2025-07-07

new をオーバーロードして、その単一引数が型または値のいずれかになれるようにするというアイデアは、このスレッドの以前の議論で既に何度も取り上げられており、いくつかの懸念も提起されています。同じ議論を繰り返す必要がないことを願って、ここにリンクを貼っておきます:

これらのリンクは以前の議論を見つけやすくするためだけに共有しています。この件について強い意見は持っていません。

コメント 177

投稿者: aclements | 投稿日: 2025-07-09

new[T]() を受け入れることに対する一つの懸念は、new の使い方が2つの異なる完全に等価な方法になってしまうことです。そして、すでに &T{} を使って new と同等のことができる場合も多いので、新しいものを割り当てる方法が3つの等価な方法になってしまいます。

コメント 178

投稿者: neild | 投稿日: 2025-07-09

new[T]() を追加するなら、一貫性のために make[T]() も追加すべきかどうかを問う必要があると思います。一方には型パラメータがあるのにもう一方にはないというのは混乱を招きます。

new[T]()make[T]() を追加する場合、推奨される形式は何かを問う必要があります。x := new(int) と書くべきか、x := new[int]() と書くべきか?新しい形式は値を指定する場合のみに使い、x := new(int)x := new[int](42) のように書くべきでしょうか?

言語を最初から設計するなら、newmake がユーザー定義関数と同じ型パラメータ構文を使う方が、一般的な一貫性のために現在のものより明らかに良いでしょう。しかし、今新しい構文を追加すると、一貫性はさらに低下します。単純な型のゼロでない値へのポインタを作成する構文を追加することには価値があると思いますが(new(int, 42)new(int(42))&int(42) のいずれも問題なく見えます)、new(int) の代替を追加すべきではないと思います。得られる価値に対して言語の変更が大きすぎます。

コメント 179

投稿者: mitar | 投稿日: 2025-07-09

今新しい構文を追加すると、一貫性はさらに低下します。

しかし、古いスタイルが非推奨であるというリンター警告を出して、コードベースを徐々に新しいスタイルに移行させることはできるのではないでしょうか。古いスタイルは後方互換性のために永遠に残しつつ?

コメント 180

投稿者: mibk | 投稿日: 2025-07-10

new[T]()make[T]() を追加する場合、推奨される形式は何かを問う必要があります。x := new(int) と書くべきか、x := new[int]() と書くべきか?新しい形式は値を指定する場合のみに使い、x := new(int)x := new[int](42) のように書くべきでしょうか?

ジェネリクスが導入されて以来ずっと感じていた小さな構文上の違和感を思い出しました。

ジェネリックなファクトリが値引数を取らない場合、次のように書く必要があります

go
m := omap.Make[int, string]()

これは組み込み形式に比べて明らかに重い感じがします

go
m := make(map[int]string)

すべてのノイズは、明示的な型引数リストの後の空の () から来ています。

これを特殊ケースとして扱い、[] がない場合に最初の括弧リストが型引数として機能するようにできないでしょうか?

go
m := omap.Make(int, string) // omap.Make[int, string]() のシュガー

これにより、ユーザー定義のジェネリックファクトリが組み込みのものと見た目も使い心地もずっと似たものになり、makenew の「特殊性」が減ります。型推論は今まで通り機能します。型を明示するのは必要な場合だけです。


これを実際に提案する勇気はなかったのですが、この議論に関連していると感じました。

コメント 181

投稿者: aclements | 投稿日: 2025-07-10

この提案はプロポーザルプロジェクトのアクティブカラムに追加され、今後は毎週のプロポーザルレビュー会議でレビューされます。 — aclements(プロポーザルレビューグループを代表して)

コメント 182

投稿者: perj | 投稿日: 2025-07-13

議論は new(T, v) か、newof[T](v) のような新しい組み込み関数のどちらかに落ち着いたようです。

この2つのうち、私はわずかに後者を好みます。既存のジェネリクス構文により適合しており、何が起きているかが十分に明確です。また、デフォルトが正しくない場合にオプションで型を指定できますが、必須ではありません。

以前、その関数を newval[T](v) と名付けたことがあります。別の名前の案として挙げておきます(命名が難しいのは皆さんご存知の通りです)。

コメント 183

投稿者: rednafi | 投稿日: 2025-07-14

new(T, v) 構文は好きではありません。これは組み込みの変更も必要です。そして後方互換性のために new(v) を残す必要があります。

これは初心者にとって混乱します。提案された newof[T](v) の方が良いです。なぜなら、人々はすでにこの形式のものを書いており、馴染みがあるからです。

また、newof という名前は既存の値を指すのではなく、新しい値のアドレスを取ることを示しています。そのため、ptr よりも良いと思います。

コメント 184

投稿者: mcandre | 投稿日: 2025-07-14

2025年になってもまだ進展なし。購読解除します。

コメント 185

投稿者: aclements | 投稿日: 2025-07-23

@adonovan、議論のまとめを書いていただけませんか?どの構文が検討され、それを却下した理由を含めて。同じ議論の蒸し返しを避けたいと思います。

コメント 186

投稿者: DeedleFake | 投稿日: 2025-07-23

@aclements

実験として、GitHub CLIを使ってこのイシューをJSONに変換し、それをLLMに入力してサマリーと各構文の賛否リストを生成しました。かなり良い仕事をしたようなので、その出力をここに貼り付けます。

GitHub Issue #45624 の概要:Goにおける単純型へのポインタの提案

このイシューは2021年4月にRob Pikeによって開かれ、単純型(例:int、string)へのポインタをより簡単に作成するための構文をGoに追加することを提案しています。複合リテラルへのポインタ(例:&S{a:3})は簡単ですが、単純型では var a = 3; p = &a のような冗長な回避策が必要になるという非対称性に対処するものです。核心的な問題は、型なし定数(例:&3)には型がなく、アドレス取得不可能な式を直接アドレス指定すると不整合やバグにつながる可能性があることです。

議論は約3.5年(2025年7月まで)に及び、コントリビュータ、コアチームメンバー(例:Rob Pike、Russ Cox、Ian Lance Taylor)、ユーザーから100以上のコメントが寄せられています。主要なテーマには、直交性、可読性、後方互換性、既存の &(割り当てなしでアドレスを取得)や new(ゼロ値を割り当て)との混乱回避が含まれます。多くのユーザーが、特にオプショナルに *int*string を使用するAWS SDKやThriftなどのAPIのために、func ptr[T any](v T) *T { return &v } のようなヘルパー関数を書いていると報告しています。

ジェネリクス(Go 1.18で導入)はヘルパーを通じて一部のニーズを軽減しますが、言語レベルの対称性とエルゴノミクスのためにこの提案は続いています。まだコンセンサスはありません。2025年7月現在、このイシューはプロポーザルレビュープロセスでアクティブです。

提案された構文と論点

以下は、カテゴリ別にグループ化された主要な提案構文をまとめた表です。各項目には、簡単な説明、例、主な支持者、賛成意見(利点)、反対意見(欠点)が含まれています。提案は元の投稿とコメントから抽出されています。一部は重複や発展があります(例:new のバリエーション)。

構文カテゴリ具体的な提案説明主な支持者利点欠点
new 組み込みの拡張new(T, expr)(元の投稿のオプション1)new にオプションの第2引数を追加:T を割り当て、expr で初期化(必要に応じて暗黙的変換)。&composite をこのシュガーとして再定義。p := new(int, 3)<br>p := new(T, f())Rob Pike(オリジナル)、Alan Donovan(最近の復活)、Ian Lance Taylor- 明示的な割り当てと型。<br>- 既存の組み込みへの小さな変更。<br>- 直交的:& をオーバーロードせずに非対称性を修正。<br>- 後方互換性あり。<br>- make(T, n) 構文と一致。<br>- 教育に明確(隠れた割り当てなし)。- 冗長/重複(型が明らかな場合に繰り返し、例:new(time.Duration, time.Second))。<br>- 推論可能でも型が必要。<br>- ポインタのコピーには役立たない(例:new(int, *x.field))。<br>- new のオーバーロード(既に1つの形式がある)。<br>- ジェネリクスヘルパーほど簡潔ではない。
new 組み込みの拡張new(expr)new をオーバーロードして型の代わりに式を受け入れる;expr から型を推論。p := new(3)*int を推論)<br>p := new(f())Russ Cox、一部のコメンター- 簡潔、型の繰り返しなし。<br>- &T{...} をシュガーとして遡及的に説明。<br>- 関数の戻り値を容易に処理。- 曖昧:読者は引数が型か値か知る必要がある(例:new(pkg.Ident) は型か定数か)。<br>- new の大幅なオーバーロード。<br>- 推論が誤りの場合に型を指定する方法がない(例:型なし定数)。
new 組み込みの拡張new[T](expr) または new[T]()ジェネリック形式:型パラメータを使用してゼロ初期化または値初期化。p := new[int](3)<br>p := new[int]()(ゼロ値)Alan Donovan(最近)、一部のジェネリクス支持者- モダンで、ジェネリクスと一貫性がある。<br>- 柔軟:推論用のオプション型パラメータ。<br>- make[T]() と統一可能。- 等価な形式が追加される(例:new(int) vs. new[int]())、変更の増加。<br>- 非ジェネリック組み込みとの不一致。<br>- ゼロ初期化には不要(既存の new と重複)。<br>- 単純なケースでの構文ノイズの増加。
アドレス変換(オプション2)&T(expr)型変換をアドレス取得可能にする:変換された値のための新しいストレージを割り当て。p := &int(3)<br>p := &string("foo")Rob Pike(オリジナル)、Ben Hoyt、Peter Bourgon- 既存の変換構文を使用。<br>- 単純型では簡潔。<br>- 常に新しいストレージを作成(変換が型を変更することと一貫性)。<br>- 一般化すれば関数の戻り値にも機能。- 混乱:& が時にコピーを割り当てる(変数への直接アドレスとは異なる)。<br>- 暗黙のキャストが型の不一致を隠す可能性(例:間違ったenum)。<br>- 変換なしでは型なし定数に使えない。<br>- 任意の式に拡張すると潜在的バグ(例:&m[k] がマップエントリをコピー)。
一般化されたアドレス取得&expr(アドレス取得不可能な式に対して)アドレス取得不可能な式(例:定数、関数の戻り値)に対して & を許可;暗黙的に割り当て。p := &3*int を推論)<br>p := &f()初期の一部コメンター、Faiface- 簡潔で直感的。<br>- 複合リテラルを一般化。<br>- 関数を自然に処理。- 主な欠点:& の動作が変わる(既存のアドレスではなくコピーを割り当て)。<br>- 型なし定数が曖昧(例:&3 の型は?)。<br>- バグの原因:&m[k] がマップをアドレス指定する代わりにコピー;インターフェースや型アサーションでの驚き。<br>- 以前のイシュー(#9097)で却下済み。
一般化されたアドレス取得&f(...)(関数の戻り値のみ)関数呼び出し/変換に限定;戻り値の新しいアドレス。p := &time.Now()<br>p := &int(3)Faiface、Ben Hoyt、Filippo Valsorda- 一般的なケースをカバー(例:戻り値)。<br>- 一貫性:関数は新しいオブジェクトを作成。<br>- 複合リテラルほど特殊ではない。- & のオーバーロード(コピーvs.直接アドレス)。<br>- すべての式をカバーしない(例:マップルックアップ)。<br>- 戻り値のセマンティクスの問題(例:複雑な関数でのコピーvs.エイリアシング)。
スカラーの複合リテラル&T{expr}単純型に複合リテラル構文を許可(暗黙の[1]Tとして扱う)。p := &int{3}Clausecker、一部のCに影響を受けたコメンター- 既存の複合構文を均一に拡張。<br>- C(複合リテラル)から馴染みがある。<br>- 新しい組み込みなし。- 冗長:int{3} == int(3);なぜ2つの方法?<br>- []interface{} のような型では曖昧(スライスかスカラーか?)。<br>- 疑問を生む:裸の T{expr} は何を意味する?<br>- 直交的でない;特殊ケースを追加。
新しい組み込み関数ptr(expr) または ref(expr)(ジェネリック)func ptr[T any](v T) *T { return &v } のような事前宣言ジェネリックを追加。p := ptr(3)<br>p := ref(x.field)Rogpeppe、Merovius、多くのユーザー(例:perj、nemith)- 言語変更不要(ジェネリクス後)。<br>- 簡潔、型推論可能。<br>- コピーをうまく処理(例:ref(*y))。<br>- 一般的なヘルパー関数を標準化(AWS、Thriftで使用)。- 事前宣言識別子の追加(肥大化?)。<br>- 命名の議論:ptr/ref は誤解を招く(コピーではなく既存への参照を暗示)。<br>- ポインタでの誤用の可能性(例:ptr(&v))。<br>- 組み込み拡張ほど「コア」ではない。
新しい組み込み関数newof(expr) または類似上記のバリアント、明確さのために「new of」を強調。p := newof(3)Earthboundkid、最近の一部- 明確:割り当て/コピーを示す。<br>- ref/ptr の混乱を回避。- 新しいキーワード;命名の議論の可能性。<br>- 上記と同じ誤用リスク。
その他/雑多&&expr「割り当ててアドレス取得」のための二重 &p := &&3Willfaught- 視覚的に既存の & を拡張。- 醜い/冗長;論理ANDと混同。<br>- 真剣に検討されていない。
その他/雑多&+expr単項 + を「右辺値化」として使い、その後アドレス取得。p := &+fooClausecker- コピーを明示的に強制。- 難解;すべての型に対する単項 + のオーバーロードを追加。<br>- 過度に巧妙/複雑。
その他/雑多&(expr)(括弧付き)割り当てアドレスのために括弧を必須にする。p := &(3)DeedleFake- 直接の &v と区別。- 既に合法(グループ化のみ);セマンティクスの変更なし。<br>- 曖昧さを解決しない。

全体的な議論の傾向

  • 賛成のコンセンサス:オプショナル(例:構造体/JSON/APIの *int)のエルゴノミクス向上;ボイラープレートヘルパーの削減;言語の対称性の強化。
  • 反対のコンセンサス:割り当てとアドレス取得の混乱のリスク;後方互換性が最重要(Go 1保証);ジェネリクスが緊急性を減らすが非対称性は排除しない。
  • 却下されたアイデア:広範な &expr(バグの原因が多すぎる);スカラー複合リテラル(冗長/曖昧)。
  • 最有力候補(2025年7月現在)new(T, expr)(明示的、小さな変更)vs. ref(expr) のようなジェネリック組み込み(簡潔、推論可能)。
  • ステータス:アクティブ;実装/プロトタイプなし。古い形式をリンターで非推奨にすることを提案する人もいるが、コアチームは最小限の変更を強調。

コメント 187

投稿者: adonovan | 投稿日: 2025-07-23

ありがとうございます、悪くないサマリーです。以下を追加したいと思います:

後半の議論の主なポイントは、状況に応じて値のみ、型のみ、またはその両方を指定できるようにしたいという要望に関するものです。

  • new(T, expr) は常に型を必要とし、多くの場合冗長です。
  • new(expr) は型も指定する方法を提供しません。
  • &T(expr)&T{expr} も常に明示的な型を必要としますが、&(expr)&{expr}&expr にはいずれも曖昧さ、可読性、または参照の非透過性の問題があります(ただし &T{x} にも同じことが言えます)。
  • new[T](expr) または new[T]() は3つのケース(型、値、両方)すべてをサポートしますが、new(T) の新しい言い方を作ります。また、new(T)new(expr) の形式は読者にとって区別が難しい場合があります。new(T)new[T]() に近代化するのは変更コストが大きいです。

言語プロポーザル委員会は当初 new(T, expr) に傾いていましたが、これは単なる簡潔さのハックであり(1行のラッパー関数をいつでも書けるので)、型を冗長に記述することを避けたいという要望には共感しています。そのため、今は最後のオプションを支持しています。

コメント 188

投稿者: earthboundkid | 投稿日: 2025-07-24

LLMのサマリーを読んで、new(expr) に傾きました。型を指定する必要がある場合は new(myint(3)) と書けます。反対意見は曖昧であるということですが、慣れるでしょう。

コメント 189

投稿者: neild | 投稿日: 2025-07-24

new[T](expr) は2つの変更を1つにまとめたものです:

  1. 単純型へのポインタを作成する式。
  2. 組み込み new 関数の型パラメータ化バージョン。

このうち、2番目の変更の方がはるかに重要です。

Go 1.1頃に初めてGoを使い始めたとき、印象的だったのは、ジェネリクスに関する「自分にはいいけど君にはダメ」という態度でした。言語には、型やいくつかの型でパラメータ化された組み込み関数や型(newappend、マップなど)が含まれていましたが、ユーザーコードでは実装できませんでした。最終的に言語はユーザー定義の型パラメータ化関数と型を獲得し、次にイテレータを獲得し、今ではユーザーコードで同等のものを書くことができます。

しかし、ユーザー定義の型パラメータ化関数と型は、組み込みのものとは見た目が異なります。New[int32]() のような関数を書けますが、組み込みは new(int32) です。Map[K,V] のような型を書けますが、組み込みは map[K]V です。

もし言語が最初からジェネリクスを含んでいたなら、組み込み関数をユーザー定義のものと同じように設計していたでしょう:new[T]()append[int32](nil, 1, 2, 3)map[K,V]chan[T] など。しかし言語にはジェネリクスがなく、組み込み関数をそのようには設計しませんでした。そのため少し不一致があります。この不一致は新しいユーザーに説明するのはかなり簡単です:「Go 1.0にはユーザー定義ジェネリクスがなかったので、組み込み関数は異なる構文を使っています。」これで問題ありません。

new[T](expr) を追加すると、その不一致を少し解消し始めます。しかし、1箇所だけ解消すると、実際には不一致の量が増えます:元の組み込みジェネリクス、新しい組み込みジェネリクス、そしてユーザー定義ジェネリクスが存在することになります。new には2つの形式があるのに、makeappend などにはないことをユーザーにどう説明すればよいでしょうか?

おそらく不一致をすべての場所で修正すべきかもしれません。make[T](...)append[T](s, v...) なども追加できます(append([]int{}, v...) の代わりに append[int](nil, v...) を使えるなら便利かもしれません。)しかしそうすると、より一貫性のある形式がより良いと暗黙的に言っていることになり(そうでなければなぜ追加したのか?)、人々にコードをその形式に移行するよう促すことになります。これは単純型の値へのポインタを作成する簡単な方法を追加するよりもはるかに広範な変更です!

そのため、new[T](expr) は好きではありません。new[T]() を追加したいなら、組み込みジェネリック関数の包括的な近代化の一環としてそれを行うべきです。その近代化は価値があるかもしれませんが(疑わしい利点のために多くの変更が必要と思いますが)、行うならこの提案の付随的な影響としてではなく、それ自体のメリットで行うべきです。

コメント 190

投稿者: Merovius | 投稿日: 2025-07-24

@neild 付け加えると、string の例外があるため、現在のGoジェネリクスでは append(や copy)の型を表現できません。

コメント 191

投稿者: ianlancetaylor | 投稿日: 2025-07-24

何かをするなら new(T, expr) から始めるべきだと現在考えています。これは現在の言語に対するシンプルで小さな拡張です。new(int32, 1) のような式では、型は冗長ではありません。構造体型については、複合リテラル構文が簡潔な代替手段として残ります。

型が冗長かつ少し面倒になるケースは、関数が返す値のアドレスを取りたい場合で、その関数が複雑な型を返す場合のようです。実際にはどのくらいの頻度で発生するのでしょうか?

new[T](expr) を使う場合、おそらく多くの場合 T が推論可能であることを示唆しています。言い換えれば、これは new(expr) の異なる構文です。人々はしばしば単に new(name) と書くので、コードを読む際に name が式なのか型なのかが不明確であるという上記の曖昧性の問題が発生します。

コメント 192

投稿者: benhoyt | 投稿日: 2025-07-25

@ianlancetaylor それは確かに明白な出発点であり、一般的なユースケース(私の場合は通常 intstring)では過度に冗長ではありません。time.Now() のようなもの(何年も前の私の元のケース)では少し重複感がありますが、1行で書けるならまだ悪くありません。私はこれで大丈夫です:

go
s := myStruct{
    intField:    new(int, 5),
    stringField: new(string, "Hello"),
    timeField:   new(time.Time, time.Now()),
}

コメント 193

投稿者: jakebailey | 投稿日: 2025-07-25

typescript-goでは、JS/JSONの null などとやり取りする必要があるLSP型を生成しているため、ptrTo を何度も使っています。

生成されたテストコードを除くと、ptrTo の106件のインスタンスのうち74件は推論可能であり、上記の提案では new(string, "...")new(bool, true)new(int, 0) のように冗長に書くことになります。

これはあまり良くないと感じており、おそらく ptrTo を使い続けることを選ぶでしょう。

コメント 194

投稿者: adonovan | 投稿日: 2025-07-30

妥協案として:ジェネリック組み込みというパンドラの箱を開けることなく、最も効率的な記法を許可するために、3つの形式すべてを許可することができます:

  • new(T) -- 既存の形式、ゼロ値
  • new(T, value) -- 明示的な型、非ゼロ値
  • new(value) -- 暗黙的な型、非ゼロ値

型チェッカーは3つのケースすべてを十分に区別できるはずです。

コメント 195

投稿者: ianlancetaylor | 投稿日: 2025-07-30

@adonovan 私の懸念は、このイシューで他の人も表明していますが、new(value) はGoプログラムを読む人にとって混乱を招く可能性があるということです。型チェッカーがそれを処理できるかどうかは問題ではないと思います。問題は、読者が new(otherpkg.X) の型と意味を容易に理解できるかどうかです。

コメント 196

投稿者: Merovius | 投稿日: 2025-07-30

その議論はあまり説得力がないと思います。現在、例えばフィールドを設定したい場合に v.F = otherpkg.X と書くことは問題ありません。F がポインタ型で v.F = new(otherpkg.X) と書く場合、それがより問題になるとは思えません。

new(expr) の型は「expr の型へのポインタ」です。したがって、expr の型が明らかな場合に限り明らかです。

[編集] ああ恥ずかしい、問題は otherpkg.X が型なのか式なのかということでした、なるほど。

解決策として、vBasicLit の場合のみ new(v) を受け入れるというのはどうでしょうか?これにより、ケースが非常に明確に区別され、解決しようとしている主要な問題にも対処できます。

コメント 197

投稿者: aclements | 投稿日: 2025-07-31

今日のプロポーザルレビューでは、&expr オプションについてかなりの時間を費やして議論しました。私が知っているGoプログラマー数人に、この構文としてどのような記法を期待するか聞いたところ、全員が &T{...} との類推で &expr と答えました。ある人は「new は全力で避けています。&T{...} を使います」とまで言いました。&expr は直感的で明白な答えのように思えますが、深刻で非自明な欠点があります。

@DeedleFake の便利なAIサマリーはこのオプションを十分に扱っていなかったので、もう少し掘り下げたいと思います。3つの欠点が挙げられていました:1.「& の動作が変わる(既存のアドレスではなくコピーを割り当て)」は問題だとは思いません。なぜなら &T{} は既にこのように動作し、非常に一般的だからです。2.「型なし定数が曖昧(例:&3 の型は?)」は、Goの既存の型なし定数のデフォルト型に関するルールを使って容易に解決できます。3.「バグの原因:&m[k] がマップをアドレス指定する代わりにコピー;インターフェースや型アサーションでの驚き」は深刻な問題に近づいています。

最後の欠点については、3つの選択肢があり、いずれも残念なトレードオフがあります:

  1. expr が任意の式である場合、expr がアドレス取得可能かどうかによって & の動作が大きく異なりますが、これは予測が難しいことが多いです。サマリーでは &m[k] の例が挙げられました:ここで m がスライスなら、k番目の要素をアドレス指定しますが、m がマップなら値をコピーして新しい割り当てを返します。

  2. expr定数式に限定できます。x が変数なのか定数なのかによって &x は依然として曖昧ですが、&m[k] の問題は解決され、アドレス取得不可能な式よりも定数式を見分ける方が一般的にはるかに容易です。ただし、これは任意の式を取って式のアドレス取得可能性に関係なく同じように動作する new(expr) のようなオプションよりも制限的です。

  3. @Merovius の提案のように、exprBasicLit に限定できます。これにより、& の動作は常に構文的に明確になりますが、非常に制限的でもあります。例えば、&int64(42) すら許可されません。

コメント 198

投稿者: DeedleFake | 投稿日: 2025-07-31

@aclements、オプション3を採用しつつ、変換に対する特殊ケースを追加することは可能ですか?

コメント 199

投稿者: jakebailey | 投稿日: 2025-07-31

上で述べた typescript-goptrTo のケースについてもっと詳しく説明すべきでした。約3分の1が ptrTo(lsproto.CompletionItemKindClass) のような形式で、通常は事前定義された定数/enum値です。

生成されたテストコードを含めるとさらに偏りが大きくなり、ptrTo の呼び出しは6000件以上になり、そのうち約3000件が ptrTo(lsproto.CompletionItemKindClass) のような形式です。BasicLit は約300件程度です。

コメント 200

投稿者: Merovius | 投稿日: 2025-07-31

@aclements

これにより、& の動作は常に構文的に明確になりますが、非常に制限的でもあります。例えば、&int64(42) すら許可されません。

しかし、&42*int64 に代入可能にすることはできると思います。これは私の意見ではさらに良いです。代入の右辺での new(expr) の特別な扱いが必要です。nil に対しては既にそうしています。あまりエレガントではありませんが、動作させるのはそれほど難しくないように思えます。

より重要な制限は、&pkg.EnumLikeConstant ができないことだと思います(@jakebailey が指摘しているように)。new(expr) の場合、new(expr) vs. new(type) の曖昧さを避けるため、これは本質的な制限になります。&expr の場合はそうではなく、expr修飾識別子にも許可できます。

ポイントは:望むなら、定数式のどのサブセットにでも式を制限できるということです。複雑さのコストは増加します。しかし、曖昧に見えるものを含まずに、望むすべてのものに便利に対応できるものを同時に得ることはできないと思います。また、議論の余地はありますが、&thing が非常に特定の thing(構造体リテラルとアドレス取得可能な式、ここで「アドレス取得可能」はおっしゃるように時に予測が難しい)に対してのみ有効であるという状況は既にあります。

また、#74472 では、BasicLit | QualifiedIdent の制限も既に検討しました。定数式の非常にシンプルなサブセットは一般的に有用かもしれません。一般的な定数式はイニシャライザーにとって非常に良い決定です。しかし、配列のサイズのようなものにまで利用可能にしたのは、やるべき以上のことだったかもしれません。

(個人的には &expr の見た目は好きではありませんが、まあ、慣れるかもしれません)


コメント 201

投稿者: adonovan | 投稿日: 2025-07-31

この提案の主な動機は、新しいint変数を作成してその値をiにするためにptrOf(i)のようなヘルパー関数(またはprotocolメッセージのユーザーにとってのproto.Int64(i))を呼び出す必要を避けることです。したがって、iの値として定数のみを許可する設計はどれも解決策にはなりません。これにより、@aclementsの&exprに関するバリアント2と3が除外され、残るのは1ですが、これは最小驚きの原則に反するというのが我々全員の合意だと思います。(&somePtrExpr.fは現在と同じように動作しますが、&someStructValueExpr.fはメモリ割り当てを行うことになります。)したがって、&exprは実現不可能だと思います。

コメント 202

投稿者: t9t | 投稿日: 2025-07-31

ある人は「私はnewを絶対に避けます。&T{...}を使います」とまで言っていました。

newの修正に反対して&を支持するこの議論は少し弱いと感じます。なぜなら、&T{}が好まれるのは、(新しい)ゼロ値のアドレスを取得するのではなく、値が設定された状態のアドレスを直接取得できる_からこそ_です。私の経験では、new(T)の用途はそもそも限られています。new(value)が許可されれば、違いは美的なものだけになるでしょう。そしておそらく、&が単純な型で使えずnewが使える場合の一貫性のために、人々は&T{}よりもnewを好むかもしれません(さらにnewという名前は、既存のもののアドレスを取得するのではなく、新しい値を作成することを明示的に示しており、&は主にアドレス取得に使われているので)。

これを&newのような既存の演算子や関数に収めようと非常に努力する特別な理由はあるのでしょうか?これ専用の新しい組み込み関数を導入しない強い理由があるはずですよね?

コメント 203

投稿者: Merovius | 投稿日: 2025-07-31

@adonovan

この提案の主な動機は、新しいint変数を作成してその値をiにするためにptrOf(i)のようなヘルパー関数(またはprotocolメッセージのユーザーにとってのproto.Int64(i))を呼び出す必要を避けることです。したがって、iの値として定数のみを許可する設計はどれも解決策にはなりません。

それは必ずしもそうは言えないと思います。そのような関数の使用のほぼすべてが定数引数で行われるのであれば、定数のみを許可する解決策で十分に思えます。

コメント 204

投稿者: adonovan | 投稿日: 2025-07-31

それは必ずしもそうは言えないと思います。そのような関数の使用のほぼすべてが定数引数で行われるのであれば、定数のみを許可する解決策で十分に思えます。

それはもっともです。私の暗黙の前提は(protobufのコードを書いた経験から)非定数引数での使用が非常に多いということでした。

コメント 205

投稿者: greg-dennis | 投稿日: 2025-07-31

ある人は「私はnewを絶対に避けます。&T{...}を使います」とまで言っていました。

これはnewを欠けているptrOf関数として再利用/転用することへの賛成の議論_として_見ることもできます。つまり、「new(x)が曖昧である」という議論への「解決策」は、スタイルガイダンスとして「new(T)は使わず、明白またはそうと証明されない限りxは式だと仮定する」とすることかもしれません。多くの開発者がすでにnew(T)ではなく&T{}のみを使っているので、彼らにとっては既存の慣行を成文化するだけのことです。この道を選ぶ場合、2引数のオーバーロードはnew(T, expr)ではなくnew(expr, T)とする方が、最初の引数が式であることの期待を強調するため、理にかなっているかもしれません。確かに、曖昧さを完全に除去できない不快な解決策ですが、不快な解決策しかない空間にいるようです。

コメント 206

投稿者: apparentlymart | 投稿日: 2025-07-31

@greg-dennisの上記のポイントに関連して、もしnew(value)を今後new(type)より優先すべきだという合意が得られた_なら_、エコシステムが適応するための妥当な期間の後、典型的な「lint」系ツールがnew(type)new(value)に置き換えることを提案し始めるでしょう。interface{}の代わりにanyを使うことを推奨するのが一般的になったのと同様です。

ただし、interface{}からanyへの変換は機械的な置換であるのに対し、new(type)からnew(value)への変換はtypeのゼロ値の書き方を考える必要があることは認めます。指定された型のゼロ値を返すジェネリック関数があれば便利でしょう -- new(zero[type]())new(zero(type)) -- ただし、https://github.com/golang/go/issues/61372 のように、その方向のアイデアはすでに何度か却下されていること、そしてそれは同じことを書く方法が複数ある状況に戻ってしまうことにも注意します。

コメント 207

投稿者: Merovius | 投稿日: 2025-07-31

new(type)をかなり頻繁に使い、その使用を擁護してきた[1]者として、今後はそれを避けるべきだと言われたら不満に思うでしょう。最終的にそれが最善であるならば従いますが、抗議しながらです。

[1] 参考までに:Structにエクスポートされたフィールドがあって初期化したい場合(例:http.Server)は&Struct{}を使い、デフォルト値を望んでいることを明示します。そうでない場合(例:bytes.Buffer)はnew(Struct)を使い、その型が不透明であることを明示します。

コメント 208

投稿者: JAicewizard | 投稿日: 2025-08-02

妥協案:ジェネリック組み込み関数のパンドラの箱を開けることなく最も効率的な表記を許可するために、以下の3つの形式すべてを許可することができます:

  • new(T) -- 既存の形式、ゼロ値

  • new(T, value) -- 明示的な型、非ゼロ値

  • new(value) -- 暗黙的な型、非ゼロ値

型チェッカーは3つのケースすべてを十分に区別できるはずです。

はるかに侵入的な変更にはなりますが、この特定のケースで_を「推論される」型として使用することができます。パターンマッチングのある言語では、_はすでに「何でも合うもの」のインジケーターとして使われることが多いですが、通常は値に対してであって型に対してではありません。言語の残りの部分との一貫性を求めるなら、var x _ = ...x := ...の冗長な形式として、func[_](x)func(x)の冗長な形式として許可することもできます。(推論可能な型パラメータを明示的に指定するとLSPが文句を言うので、多くの呼び出しがある関数で一部が自動推論可能な場合、後者は実際に使うかもしれません。すべてに[...]を付けたいけれど、_がそのための「分かってるから黙って」という値になり得ます)

コメント 209

投稿者: adonovan | 投稿日: 2025-08-06

new(value)と等価な呼び出しを見つける簡易アナライザーを作成し、コーパス内の25Kモジュールのサンプルで実行しました。new(value)と等価な関数が5K個、呼び出しが700K個見つかりました。アナライザーのソースはこのメモの末尾にあります。

それぞれのランダムな25件を以下に示します。添付ファイルには関数の完全なリストと呼び出しの10Kランダムサブセット(GitHubのアップロード制限のため)が含まれています。

関数一覧 funcs.txt

https://go-mod-viewer.appspot.com/git.zd.zone/hrpc/[email protected]/types/basic.go#L11: Int64はnew(value)関数です https://go-mod-viewer.appspot.com/gitee.com/larksuite/oapi-sdk-go/[email protected]/core/utils.go#L37: StringPtrはnew(value)関数です https://go-mod-viewer.appspot.com/github.com/agrea/[email protected]/ptr.go#L16: Float32はnew(value)関数です https://go-mod-viewer.appspot.com/github.com/akamai/AkamaiOPEN-edgegrid-golang/[email protected]/pkg/imaging/policy.gen.go#L1992: IfDimensionDimensionPtrはnew(value)関数です https://go-mod-viewer.appspot.com/github.com/alibabacloud-go/[email protected]/tea/trans.go#L14: Intはnew(value)関数です https://go-mod-viewer.appspot.com/github.com/chanxuehong/[email protected]/util/helper.go#L29: Float32はnew(value)関数です https://go-mod-viewer.appspot.com/github.com/clerkinc/[email protected]/clerk/http_utils_test.go#L62: int64ToPtrはnew(value)関数です https://go-mod-viewer.appspot.com/github.com/cznic/[email protected]/all_test.go#L61: bytePtrはnew(value)関数です https://go-mod-viewer.appspot.com/github.com/google/go-github/[email protected]/github/github.go#L1754: Intはnew(value)関数です https://go-mod-viewer.appspot.com/github.com/hashicorp/[email protected]/clients/cloud-billing/stable/2020-11-05/models/billing_account_on_demand_status.go#L23: NewBillingAccountOnDemandStatusはnew(value)関数です https://go-mod-viewer.appspot.com/github.com/hupe1980/[email protected]/huggingface.go#L207: PTRはnew(value)関数です https://go-mod-viewer.appspot.com/github.com/Ingenico-ePayments/[email protected]/examples/merchant/productgroups/Helper.go#L25: newInt32はnew(value)関数です https://go-mod-viewer.appspot.com/github.com/intel-go/[email protected]/decode_test.go#L225: sliceAddrはnew(value)関数です https://go-mod-viewer.appspot.com/github.com/kaptinlin/[email protected]/struct_validation_test.go#L97: boolPtrはnew(value)関数です https://go-mod-viewer.appspot.com/github.com/livekit/[email protected]/utils/pointer/to.go#L17: Toはnew(value)関数です https://go-mod-viewer.appspot.com/github.com/lmittmann/[email protected]/internal/abi/copy_test.go#L313: ptrはnew(value)関数です https://go-mod-viewer.appspot.com/github.com/openshift-online/[email protected]/helpers/json_helpers.go#L70: NewIntegerはnew(value)関数です https://go-mod-viewer.appspot.com/github.com/pachyderm/[email protected]/src/server/pkg/deploy/assets/assets.go#L319: replicasはnew(value)関数です https://go-mod-viewer.appspot.com/github.com/sacloud/libsacloud/[email protected]/sacloud/pointer/primitive.go#L39: NewUintはnew(value)関数です https://go-mod-viewer.appspot.com/github.com/stripe/stripe-go/[email protected]/stripe.go#L1259: Stringはnew(value)関数です https://go-mod-viewer.appspot.com/github.com/TykTechnologies/[email protected]/internal/connect_reply.go#L130: uintPtrはnew(value)関数です https://go-mod-viewer.appspot.com/github.com/Uptycs/[email protected]/gen/osquery/osquery.go#L103: ExtensionRouteTablePtrはnew(value)関数です https://go-mod-viewer.appspot.com/github.com/vmware/[email protected]/vim25/json/discriminator_test.go#L763: addrOfUint32Noopはnew(value)関数です https://go-mod-viewer.appspot.com/gitlab.com/ignitionrobotics/web/[email protected]/types.go#L14: Float64はnew(value)関数です https://go-mod-viewer.appspot.com/google.golang.org/[email protected]/xds/internal/xdsclient/xdsresource/unmarshal_rds_test.go#L1607: newBoolPはnew(value)関数です

呼び出し一覧 calls.txt

https://go-mod-viewer.appspot.com/github.com/aliyun/[email protected]/integration/proxy/proxy_test.go#L34: new(value)類似関数Stringの呼び出し https://go-mod-viewer.appspot.com/github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/[email protected]/fake/adminrules_server.go#L323: new(value)類似関数Ptrの呼び出し https://go-mod-viewer.appspot.com/github.com/biogo/[email protected]/io/featio/gff/gff_test.go#L58: new(value)類似関数floatPtrの呼び出し https://go-mod-viewer.appspot.com/github.com/google/go-github/[email protected]/github/github-stringify_test.go#L608: new(value)類似関数Intの呼び出し https://go-mod-viewer.appspot.com/github.com/google/go-github/[email protected]/github/event_types_test.go#L10643: new(value)類似関数Stringの呼び出し https://go-mod-viewer.appspot.com/github.com/google/go-github/[email protected]/github/event_types_test.go#L11130: new(value)類似関数Stringの呼び出し https://go-mod-viewer.appspot.com/github.com/google/go-github/[email protected]/github/repos_hooks_deliveries_test.go#L288: new(value)類似関数Stringの呼び出し https://go-mod-viewer.appspot.com/github.com/google/go-github/[email protected]/github/event_types_test.go#L1509: new(value)類似関数Stringの呼び出し https://go-mod-viewer.appspot.com/github.com/google/go-github/[email protected]/github/enterprise_actions_runners_test.go#L142: new(value)類似関数Stringの呼び出し https://go-mod-viewer.appspot.com/github.com/google/go-github/[email protected]/github/activity_test.go#L167: new(value)類似関数Stringの呼び出し https://go-mod-viewer.appspot.com/github.com/google/go-github/[email protected]/github/event_types_test.go#L7953: new(value)類似関数Stringの呼び出し https://go-mod-viewer.appspot.com/github.com/google/go-github/[email protected]/github/event_types_test.go#L9960: new(value)類似関数Intの呼び出し https://go-mod-viewer.appspot.com/github.com/google/go-github/[email protected]/github/event_types_test.go#L6144: new(value)類似関数Stringの呼び出し https://go-mod-viewer.appspot.com/github.com/google/go-github/[email protected]/github/event_types_test.go#L1201: new(value)類似関数Stringの呼び出し https://go-mod-viewer.appspot.com/github.com/google/go-github/[email protected]/github/security_advisories_test.go#L1518: new(value)類似関数Boolの呼び出し https://go-mod-viewer.appspot.com/github.com/google/go-github/[email protected]/github/teams_discussions_test.go#L562: new(value)類似関数Stringの呼び出し https://go-mod-viewer.appspot.com/github.com/google/go-github/[email protected]/github/github-stringify_test.go#L869: new(value)類似関数Stringの呼び出し https://go-mod-viewer.appspot.com/github.com/google/go-github/[email protected]/github/github-stringify_test.go#L635: new(value)類似関数Ptrの呼び出し https://go-mod-viewer.appspot.com/github.com/google/go-github/[email protected]/github/repos_statuses_test.go#L208: new(value)類似関数Ptrの呼び出し https://go-mod-viewer.appspot.com/github.com/google/go-github/[email protected]/github/gists_test.go#L124: new(value)類似関数Ptrの呼び出し https://go-mod-viewer.appspot.com/github.com/google/go-github/[email protected]/github/enterprise_actions_runner_groups_test.go#L305: new(value)類似関数Ptrの呼び出し https://go-mod-viewer.appspot.com/github.com/google/go-github/[email protected]/github/event_types_test.go#L7201: new(value)類似関数Ptrの呼び出し https://go-mod-viewer.appspot.com/github.com/google/go-github/[email protected]/github/git_refs_test.go#L311: new(value)類似関数Ptrの呼び出し https://go-mod-viewer.appspot.com/github.com/mattermost/mattermost-server/[email protected]/store/storetest/group_store.go#L4018: new(value)類似関数NewStringの呼び出し https://go-mod-viewer.appspot.com/github.com/tsuna/[email protected]/hrpc/hrpc_test.go#L546: new(value)類似関数Boolの呼び出し

go

var Analyzer = &analysis.Analyzer{
	Name:      "newvalue",
	Doc:       "xxx",
	URL:       "yyy",
	Run:       run,
	FactTypes: []analysis.Fact{&newLike{}},
}

func run(pass *analysis.Pass) (any, error) {
	// pass 1. export facts
	for _, file := range pass.Files {
		ast.Inspect(file, func(n ast.Node) bool {
			switch n := n.(type) {
			case *ast.FuncDecl:
				fn := pass.TypesInfo.Defs[n.Name].(*types.Func)
				if n.Body != nil && len(n.Body.List) == 1 {
					if ret, ok := n.Body.List[0].(*ast.ReturnStmt); ok && len(ret.Results) == 1 {
						if unary, ok := ret.Results[0].(*ast.UnaryExpr); ok && unary.Op == token.AND {
							if id, ok := unary.X.(*ast.Ident); ok {
								if v, ok := pass.TypesInfo.Uses[id].(*types.Var); ok {
									sig := fn.Signature()
									if sig.Results().Len() == 1 &&
										is[*types.Pointer](sig.Results().At(0).Type()) && // => no iface conversion
										sig.Params().Len() == 1 &&
										sig.Params().At(0) == v {
										pass.ReportRangef(n, "%s is a new(value) func", n.Name)
										pass.ExportObjectFact(fn, &newLike{})
									}
								}
							}
						}
					}
				}
			}
			return true
		})
	}

	// 2. report calls
	for _, file := range pass.Files {
		ast.Inspect(file, func(n ast.Node) bool {
			if call, ok := n.(*ast.CallExpr); ok {
				var fact newLike
				if fn, ok := typeutil.Callee(pass.TypesInfo, call).(*types.Func); ok &&
					pass.ImportObjectFact(fn, &fact) {
					pass.ReportRangef(call, "call to new(value)-like func %s", fn.Name())

				}
			}
			return true
		})
	}
	return nil, nil
}

func is[T any](x any) bool {
	_, ok := x.(T)
	return ok
}

type newLike struct{}

func (newLike) AFact() {}

コメント 210

投稿者: andig | 投稿日: 2025-08-06

...そしてもちろん samber/lolo.ToPtrもあります。

コメント 211

投稿者: alnr | 投稿日: 2025-08-06

このスレッドの多くの方と同様に、しばらく購読しており、議論のほとんどまたはすべてを読んでいます。

最初は&"literal"のような構文が一番良いと思っていましたが、それには多くの悪い結果が伴います。

多くの方と同様に(おそらく)、避けられるなら新しいキーワード/組み込み関数を追加しないことにも賛成です。そのためnew(value)はそれほど悪くないように思えます。しかしそれにも多くの悪い結果が伴うことが判明しました。特にnew(otherpkg.Type)new(otherpkg.Variable)の区別です。

そこで、思い切って以下を提案すべきだと思います:

  • newOf組み込み関数を追加し、定義はfunc newOf[T any](t T) *T { return &t }
  • string、int、float(およびimaginary?)については、var i *int64 = newOf(123)を許可する。つまりint*からint64*への暗黙的な変換。

そう遠くない過去に、組み込み関数minmaxが追加され、非常に便利です。言語の観点からは大きな変更ではありませんでしたが、生活の質の良い向上でした。

newOfnew&よりやや見た目が悪いという点を除けば、私の理解では問題をきれいに解決します。そしてnewOfは値がコピーされること、つまりコピー/メモリ割り当てが発生しポインタは新しいメモリ位置を指すことも非常に明確にします。

コメント 212

投稿者: josharian | 投稿日: 2025-08-06

@alnr 私なら別の色に塗るかもしれません(例えばptrptrTo)が、minmaxとの類推はかなり説得力があります。

コメント 213

投稿者: adonovan | 投稿日: 2025-08-07

そのためnew(value)はそれほど悪くないように思えます。しかしそれにも多くの悪い結果が伴うことが判明しました。特にnew(otherpkg.Type)new(otherpkg.Variable)の区別です。

これが本当の問題かどうかはまだ確認されていません:おそらくほぼすべてのケースで、otherpkg.Xが型か値かはコンテキストから明らかでしょう。

string、int、float(およびimaginary?)については、var i *int64 = newOf(123)を許可する。つまりint*からint64*への暗黙的な変換。

これは式自体からではなく、式のコンテキストから型を推論することを意味します。現在のところ、言語全体でこれを行っている箇所は (a) ネストされた複合リテラル(ただし最外のリテラルには常に明示的な型があり、物事を単純にしています)、そして (b) x << yシフト式で、これは仕様と実装の最も複雑な側面の一つです。 トップダウン型推論を一般化するためのオープンな提案(#61712)はありますが、それらはすべてGoの型の理論への大きな変更を伴います。ですからここで気軽にその扉を開けないようにしましょう。

そう遠くない過去に、組み込み関数minmaxが追加され、非常に便利です。

新しい組み込み関数は簡単でしょう。しかし、これが既存の組み込み関数に安全に収められるかどうか、まだ検討し尽くしていません。

また:

  • new(T, value) -- 明示的な型、非ゼロ値

@aclementsが昨日指摘しましたが、この2引数形式のnewは不要です。なぜなら常にnew(T(value))と書けるからで、これは最大限に明確で簡潔です。

コメント 214

投稿者: Merovius | 投稿日: 2025-08-07

これは式自体からではなく、式のコンテキストから型を推論することを意味します。現在のところ、言語全体でこれを行っている箇所は

ここで重要なこととして、(型なし)定数に対してもそうしています。そしてnilに対しても。「これを行っている箇所がたった4つ」になった時点で、その扉は開いていると思います。

コメント 215

投稿者: Merovius | 投稿日: 2025-08-07

それはもっともです。私の暗黙の前提は(protobufのコードを書いた経験から)非定数引数での使用が非常に多いということでした。

ところで、opaque protobuf APIはプリミティブ型へのポインタを構築する必要性を排除していることは注目に値するかもしれません、と私は理解しています。それがこの提案の一般的な有用性を完全に消すわけではありませんが、protobufが主な課題の一つであったと思います。

コメント 216

投稿者: adonovan | 投稿日: 2025-08-07

ここで重要なこととして、(型なし)定数に対してもそうしています。そしてnilに対しても。「これを行っている箇所がたった4つ」になった時点で、その扉は開いていると思います。

それは実装の特性であり、仕様に固有のものではないと思います。つまり、0とnilの型は「型なしゼロ」と「型なしnil」であり、var ( _ int = 0; _ *int = nil )で使用される際に暗黙の変換があります。残念ながらgo/typesはこれらのケースでLHSの型をTypeOf(RHS)にプッシュダウンしています。この動作をリクエストしたのは後悔しています。すべての暗黙の変換を記録するよう求めていれば、より綺麗でずっと有用だったでしょう。

そして複合リテラルのケースは常に明示的な型を分解するため、本当に単純です。ですから残るのはシフトだけで、シフトの動作方法をさらなるトップダウン型推論の正当化として使うべきではありません。

コメント 217

投稿者: alnr | 投稿日: 2025-08-07

これは式自体からではなく、式のコンテキストから型を推論することを意味します。現在のところ、言語全体でこれを行っている箇所は

var i *int64 = newOf(123)は許可しますが、var j int; var i *int64 = newOf(j);は許可しません。基本的に引数が型なし定数の場合です。

しかし、実装やルールが複雑になりすぎるなら、必ずしも必要ではありません。

var j int; var i *int64 = newOf(int64(j));で単純に実現できます。

これが本当の問題かどうかはまだ確認されていません:おそらくほぼすべてのケースで、otherpkg.Xが型か値かはコンテキストから明らかでしょう。

以下を考えてみてください

go
package foo

type Enum int

var (
  Enum1 Enum = iota
  Enum2 Enum
)

...

package bar

var e1 = new(foo.Enum) // OK
var e2 = new(foo.Enum1) // これもOK、セマンティクスは非常に異なるが、見た目は極めて似ている

new(T, value)は一見良さそうですが、このかなり一般的なシナリオではnewOf(または現在皆が持っているカスタムヘルパー)より悪くなります:

go
package complicatedname

type ComplicatedEnumChooseMethod int

var (
  ComplicatedEnumChooseMethodSomething ComplicatedEnumChooseMethod = iota
  ComplicatedEnumChooseMethodAnything ComplicatedEnumChooseMethod
)

type Option struct {
  Method *ComplicatedEnumChooseMethod
}

func Fun(o Option) { ... }

...

package main

import "complicatedname"

complicatedname.Fun(complicatedName.Option{
  Method: new(complicatedName.ComplicatedEnumChooseMethod, complicatedname.ComplicatedEnumChooseMethodSomething),
})

// 対して

complicatedname.Fun(complicatedName.Option{
  Method: newOf(complicatedname.ComplicatedEnumChooseMethodSomething),
})

コメント 218

投稿者: adonovan | 投稿日: 2025-08-07

ところで、opaque protobuf APIはプリミティブ型へのポインタを構築する必要性を排除していることは注目に値するかもしれません、と私は理解しています。それがこの提案の一般的な有用性を完全に消すわけではありませんが、protobufが主な課題の一つであったと思います。

その通りですが、スキーマでオプショナル性を表現するためにポインタを使用するさまざまな関連状況でこの問題は依然として発生するため、JSONなどの他のエンコーディングでも一般的です。

コメント 219

投稿者: alnr | 投稿日: 2025-08-07

ところで:なぜこれはOKで

var _ = &([]string{"foo"})

これはダメなのですか?

var _ = &(time.Now())

コメント 220

投稿者: adonovan | 投稿日: 2025-08-07

ところで:なぜこれはOKで

var _ = &([]string{"foo"})

これはダメなのですか?

var _ = &(time.Now())

あなたの質問への答えは、仕様がそう定めているからです。あなたの質問が提起するより大きな問題は、まさにこの提案が答えようとしていることです。

コメント 221

投稿者: alnr | 投稿日: 2025-08-07

&(int(123))&(123)&(time.Now())を許可することの問題は何でしょうか?

&otherpkg.Variableを行う際に新しいメモリ位置を指さないため、潜在的にエラーが起きやすいというのは理解しています。しかしそれ以外には?

以前に議論されていたらすみません。このスレッドでこれだけ多くの返信があると、すべての側面を頭に留めておくのはかなり難しいです。

コメント 222

投稿者: aidandj | 投稿日: 2025-08-07

string、int、float(およびimaginary?)については、var i *int64 = newOf(123)を許可する。つまりint*からint64*への暗黙的な変換。

var i := newOf(int64(123))が同じタイプ量でより明示的なので、これは不要に感じます。

私は単に新しい構造を支持します。Toメソッドを持つptr標準ライブラリパッケージでもいいでしょう。最近Azure SDKのものを使っていますが、手元にあることが多いので。どれでも機能します。より単純で再利用可能な式を標準化することがこのイシューの意図ですよね?

コメント 223

投稿者: t9t | 投稿日: 2025-08-07

@alnr &バリアントを議論している最も包括的なコメントはこれだと思います:https://github.com/golang/go/issues/45624#issuecomment-3138414707

私には、別の構文(例えば新しい組み込み関数やnewの再利用)で避けられるいくつかの新しい落とし穴を導入することになるように感じます。


@josharian

私なら別の色に塗るかもしれません(例えばptrptrTo)が、minmaxとの類推はかなり説得力があります。

ptrptrToは、その関数が値へのポインタを返すのではなく、新しい値を作成してその新しい値へのポインタを返す場合、誤解を招く名前です(https://github.com/golang/go/issues/45624#issuecomment-3046301760 も参照)。

コメント 224

投稿者: aclements | 投稿日: 2025-08-07

私の経験では、new(T)の用途はそもそも限られています。new(value)が許可されれば、違いは美的なものだけになるでしょう。(https://github.com/golang/go/issues/45624#issuecomment-3139948536)

これがまさにあなたの言っていることかは分かりませんが、人々が今日new(T)を使って_いない_ということは、newを再定義するのに好都合だという議論があります。私にとって、そして多くの人にとって、newは常にイボのように感じられてきたので、それをもっと有用なものに変えてはどうでしょうか?

コメント 225

投稿者: aclements | 投稿日: 2025-08-07

new(value)と等価な関数が5K個、呼び出しが700K個見つかりました。(https://github.com/golang/go/issues/45624#issuecomment-3161481726)

これらの数値を取得してくれてありがとうございます、@adonovan!この機能を支持する上で、これらの統計は非常に説得力があると思います。

github.com/google/go-githubが異常に過剰に表れているという明らかなデータの問題があることに注意します。(これについてはオフラインで話しました。)呼び出しについて、ユニークなモジュール数だけを取得できますか?go-githubはおそらくそのうち約74を占めるでしょうが、より有益かもしれません。関数宣言については、sed 's/@.*//' funcs.txt | sort | uniq | wc -lで715のユニークなモジュールが報告されます。


コメント 226

投稿者: ianlancetaylor | 投稿日: 2025-08-07

この統計は、この機能を支持する上で非常に説得力があると感じます。

はい、@adonovan がそれらを取得してくれたことに感謝します。とはいえ、誰もこの機能自体に反対しているわけではありません。この議論は、使用する名前や構文についてのものです。

new(v) が既存の new(T) と紛らわしいことについて、私は引き続き懸念しています。さまざまな人々が指摘しているように(例えば、https://github.com/golang/go/issues/45624#issuecomment-1582248350 )、私たちは値と型を混同することはありません。(C++ や Java では new は値ではなく型を取ることも注目に値するでしょう。)

somefunction(v) があれば new(T, v) は不要であるという点に同意します。

提案されている関数以外の構文はいずれも説得力がないという点にも同意します。

新しい組み込み関数よりも標準ライブラリの関数のほうが望ましいと考えています(おそらく unique.Ptr?)。しかし、minmax でその議論に負けましたし、ここでも負けると思います。

したがって、私としては new(T, v)FN(v) のいずれかが示唆されます。ここで FNptrrefaddr、あるいはもっと気の利いた名前になり得ます。

コメント 227

投稿者: rogpeppe | 投稿日: 2025-08-07

したがって、私としては new(T, v)FN(v) のいずれかが示唆されます。ここで FNptrrefaddr、あるいはもっと気の利いた名前になり得ます。

念のため言っておくと、私は new(T, v) 形式が嫌いだということを(改めて?)述べておきます。なぜなら T 引数はほぼ常に冗長であり、入力が面倒で、コードの乱雑さを増すからです。

現在、必要な場所すべてにこの関数をコピー&ペーストしています:

func ref[T any](x T) *T { return &x }

名前にはこだわりはありません。ref を好むのは、長年の慣れに根ざしていると思います。

コメント 228

投稿者: chai2010 | 投稿日: 2025-08-07

私が最初に new(T, value) のアイデアを提案してから(issue9097)、10年が経ちました。全員が問題だと同意しているにもかかわらず、議論は5年前と同じままです。元の提案に対する支持と理解を示してくださった @ianlancetaylor に深く感謝します。

もう待ちきれなかったので、Go の進化に基づいた私の Wa 言語に new(T, value) 構文を実装しました:

wa
func main {
	p := new(int, 123)
	println(*p)
}

これで、感情的な荷物なしに双方の違いを理解できるようになりました。

いつか公式にサポートされることを願っています。

コメント 229

投稿者: adonovan | 投稿日: 2025-08-08

@chai2010、私たちは後悔するような間違いを避けるためにゆっくり進めています。これを追加しないコストは、単純なワンライナーで書けることを考えると非常に低いです。ここで余計な荷物を持ち込んでいるのは誰なのか、私にはよくわかりません。

@rogpeppe、@ianlancetaylor:new(v) に関する懸念は理解しています。実際に問題になるかどうかはまだ確信が持てませんが、明確な境界線をぼやかすことは認めます。

新しい関数を追加すると仮定すると、通常のライブラリ関数にできることは明らかです(これまでそうだったのですから)。ただし、pkg.Func(v) が冗長すぎて使用の妨げにならない(さらに悪いことに、pkg をドットインポートする誘惑にならない)程度に短い名前のものでなければなりません。unique.Ptr はそのカテゴリに該当します。さらに重要なことに、new は unique パッケージとは無関係です。また、new(v) は非常に基本的で低レベルな概念なので、min や max の場合よりもさらに組み込み関数にすべき根拠が強いと思います。

組み込み関数を追加するなら、newOf(v) または varOf(v) と呼ぶでしょう。新しい変数を作成するからです。他の様々な名前(ref、ptr、addr)はそれを表現できていないため、好みません。

しかし、曖昧さが実際の問題にならないことがわかれば、やはり new(v) が望ましいです。

コメント 230

投稿者: Merovius | 投稿日: 2025-08-08

@adonovan

それは実装の特性であり、仕様に固有のものではないと思います。つまり、0 と nil の型は「型なしゼロ」と「型なし nil」であり、var ( _ int = 0; _ *int = nil ) で使用されるとき暗黙の変換があります。

同意しません。例えば、代入可能性を見ると(ここで最も気にする部分です)、nil と型なし定数の両方について、明示的な特殊ケースが設けられていることがわかります。特に前者は かなり 特殊です。仕様は定数や nil に型を割り当てて、型 X の変数を型 Y の変数に代入するように扱うのではありません。型なし定数や事前宣言された識別子 nil が代入時にどのように振る舞うかを具体的に説明しています。

「x は組み込み関数 new への呼び出しであり、T に代入可能な式を持つ」というケースを追加することは、法外に複雑な変更には思えません。

(もちろん、new(expr) の他の綴り方、例えば &expr にも同じことが当てはまります)

残念ながら go/types はこれらのケースで LHS の型を TypeOf(RHS) にプッシュダウンします。

それは実装の特性であり、仕様に固有のものではないと思います(すみません、言わずにはいられませんでした)。

コメント 231

投稿者: Merovius | 投稿日: 2025-08-08

@artificial-aidan

string、int、float(そして imaginary?)に対して、var i *int64 = newOf(123) を許可する。つまり *int から *int64 への魔法の変換。

var i := newOf(int64(123)) が同じ量のタイピングでより明示的であることを考えると、これは不要に感じます。

次のケースを考えてください:

go
package otherpkg

type MyEnumType int

const (
    MyEnumValue1 MyEnumType = iota
)

type MyStruct struct {
    Field *MyEnumType
}

すると otherpkg.MyStruct{ Field: new(otherpkg.MyEnumType(otherpkg.MyEnumValue1)) }otherpkg.MyStruct{ Field: new(otherpkg.MyEnumValue1) } よりもタイピング量(そして読む量)がかなり多くなります。

コメント 232

投稿者: atdiar | 投稿日: 2025-08-08

@adonovan 「理論的には」(最も広い意味で)、new(v)new(T) はそれほど問題なく説明できると思います。

型を値の集合と考えると、型付きの値はその型の部分型であり、その部分型には単一の値しか含まれません。シングルトンです。(長い説明ですね。)

したがって、new が型のゼロ値のアロケーションへのポインタを返すのであれば、new(v)new(T) は同じ定義から派生します。

もちろん、Go でどのように仕様化するかは、あなたへの練習問題として残しておきます ;D

コメント 233

投稿者: josharian | 投稿日: 2025-08-08

新しい変数を作成するので、newOf(v) または varOf(v) と呼ぶでしょう。

おっしゃることはわかります。しかし、実際にはこれを定数以外で使う理由はあるのでしょうか?

コメント 234

投稿者: fabiopicchi | 投稿日: 2025-08-08

ジェネリクス後の Go に newOf のような新しい組み込み関数を追加しても、言語にはあまり貢献しません。@rogpeppe が述べたように、彼の ref 関数が問題を便利に解決します。標準ライブラリに追加されることもあり得ますが、それは別の提案で議論されるべきだと思います。

標準ライブラリに refptr が存在するかどうかに関わらず、new(T, value) は追加されるべきだと思います。元の提案で述べられているように、問題は &S{} が利便性のために追加され、そのため構造体へのポインタの割り当てが基本型へのポインタの割り当てよりも便利になっていることです。new(S, S{a: 3}) はやや面倒ですが、&S{a: 3} がすでに存在するため、プログラマがより冗長な形式を使うかどうかは完全に任意です。

個人的には、new(int, 4) がとても好きです。直交的で、割り当てが明示的であり、人々はあまり new を使いませんが、私は使っていて、ヒープ割り当てが起こっていることを理解するのにあまり考えなくてよいという点が好きです。

new(int, 4)func ref[T any](x T) *T { return &x } が共存できると考える理由は、Go にジェネリクスが存在していても、プログラマが複雑すぎると判断すればジェネリクスを使わない選択ができるからです。これは「ジェネリクス前」のレガシー Go を維持・改善するものだと考えています。この Issue で誰かが、Go が最初からジェネリクスを持っていたら newmakeappend やその他の組み込み関数は存在しなかったかもしれない(つまりジェネリック関数になっていただろう)と述べていました。しかし、これらの組み込み関数とジェネリクスが言語に存在する以上、両方を追加できると感じています。

コメント 235

投稿者: zephyrtronium | 投稿日: 2025-08-08

おっしゃることはわかります。しかし、実際にはこれを定数以外で使う理由はあるのでしょうか?

ポインタ型の変数 p があり、その内容のコピーへの新しいポインタを作りたい場合、ref(*p) が最もシンプルな方法です。私が最近書いているコードでは──つまり、protobuf や AWS SDK を使っていないコードでは──定数値へのポインタを作成するよりもこちらの使い方のほうがむしろ一般的です。

(この関数を ref と名付けているのが自分だけではないと知れて嬉しいです。)

コメント 236

投稿者: adonovan | 投稿日: 2025-08-08

[@Merovius] 仕様は定数や nil に型を割り当てて、型 X の変数を型 Y の変数に代入するように扱うのではありません。型なし定数や事前宣言された識別子 nil が代入時にどのように振る舞うかを具体的に説明しています。

おっしゃる通り、型なし nil は特殊です。なぜなら、言語全体でその型を持つ式はたった1つ、nil と綴られる識別子だけだからです。しかし、いわゆる「型なし」定数は実際には型であり、それらの型を持つ式にはさまざまな形式があります(例:3.14、math.Pi、pi、3 + 0.14 など)。new(3) のような非定数式にこのような新しいカテゴリを作成することは、仕様と型システムへの大きな変更になるでしょう。new(3) を型なし nil と同様に狭く特別に扱うことも同様です。

残念ながら go/types はこれらのケースで LHS の型を TypeOf(RHS) にプッシュダウンします。 それは実装の特性であり、仕様に固有のものではないと思います(すみません、言わずにはいられませんでした)。

ええ、そこに異論はありません。

コメント 237

投稿者: Merovius | 投稿日: 2025-08-08

@adonovan 私にはかなり小さな変更に思えます。仕様で数行程度で済むでしょう。しかし、意見の相違はあるでしょうね。

はっきりさせておくと、誰もこのアイデアを 好き である必要はありません。ただ、これが仕様の現在の動作からの劇的な逸脱であると主張するのは不誠実だと思えるのです。仕様にはそのような特殊ケースが満載です。そのほとんどを、日常のコーディングでは気づくことすらありません。なぜなら、結果としてかなり直感的だからです。もしこれが直感的ではないと考えるなら、それはそれでいいでしょう。しかし、それは現在の仕組みからの大きな変更だからではないと思います。

コメント 238

投稿者: Merovius | 投稿日: 2025-08-08

この点にこだわっている理由は、これまでに2回、問題のない構文提案が、例えば new(1)*int64 に代入できないという議論で却下されてきたからです。

new をこのように特殊化することはコストであることに同意します。人々が学ばなければならない、やや奇妙なものになるでしょう¹。しかし、私の考えでは、そのコストは毎回型のフルネームを使わなければならないことよりも低いです。なぜなら、型名は有用に思えず、しばしばかなり長くなり得るからです。例えば、proto がこれの一般的なユースケースだと話しています(opaque API は別として)。new(controlpb.IntelligenceConfig_EditionConfig, IntelligenceConfig_EDITION_CONFIG_UNSPECIFIED) と入力(または読む)必要があったら、かなりイライラするでしょう。

代入可能性の問題を解決するための追加ステップをためらっているという理由だけで、良い構文提案の選択肢を捨てたくないのです。

[1] ただし、実際には 既存の定数ルールより奇妙ではないと主張したいところです。

コメント 239

投稿者: adonovan | 投稿日: 2025-08-08

私の考えでは、そのコストは毎回型のフルネームを使わなければならないことよりも低いです。

参考までに、Rog Peppe の 3月7日のコメント で、型と値を冗長に記述する必要がないことが重要であると既に納得しました。つまり new(T, v) は解決策にならないということです。そのため、new(value) に関心を持っています。必要であれば newOf や varOf と綴り、値の型が間違っている場合は new(T(v)) を使います。

ところで、あなたの enum の例では、値は正しい型を持っているので、new(IntelligenceConfig_EDITION_CONFIG_UNSPECIFIED) だけで済むはずです。一体何について意見が分かれているのでしょうか。おそらく、値なしで x.f = new() とだけ書き、コンテキストから型を推論したいということでしょうか?

トップダウン型推論ルールは重大な概念的複雑さであると本当に思います。たとえすでにそのルビコン川を渡ってしまっているとしても、これほど重要でない機能でさらに推し進めることを正当化できるとは思いません。

コメント 240

投稿者: jonathansharman | 投稿日: 2025-08-08

私は Go コンパイラのメンテナではなく、ただのユーザーですが、個人的には定数や nil と同様に new(value)(または同等のもの)の型付けを特殊化することに問題は感じません。ほとんどのユーザーにとって、これは実装の観点から理解する必要のない、ただ動く透過的な機能になると思います。この機能が紛らわしい動作を引き起こし得るコード例を誰か示せるか興味があります。

new を値に対して再利用することには少し違和感があります。一般的に変数名を型名と混同することはあまり心配していませんが、それが現実的に起こり得る場面の一つは、ローカル変数が型名をシャドウする場合です。(すでに言及されていたら申し訳ありません。スレッドが長いので。)組み込み関数には別の名前を選ぶことにやや賛成ですが、どちらの選択肢でもうまくいくと思います。

コメント 241

投稿者: atdiar | 投稿日: 2025-08-08

@jonathansharman ローカル変数名が型名を再利用する場合、それが問題になるのは せいぜい 次のように定義された場合だけだと思います: func(int int){...} または var int int。しかし var int someothertype の場合は問題ありません。 そして、外側のスコープで定義された変数に(ローカルスコープで)new(int) を *int 型の変数に代入したい場合、ゼロ値の代わりに、int に格納されていたものを指すことになります。 古いコードはこの影響を受けません。なぜなら、これを試みるとコンパイルエラーになるからです(再定義された int は型ではありません)。 では新しいコードの場合は?ありそうにありません。

それ以外については、 var i *int64 = new(1) vs. var i *int64 = new(int64(1))

後者が特に不快だとは思いません。 x.f = new(int64(3)) の場合はむしろ明確かもしれません。 コードを読むとき、冗長な情報が役立つケースもあります。 また、引数が定数の場合に new の型推論アルゴリズムに穴を開けることが、それほど簡単だとも思えません。

コメント 242

投稿者: rogpeppe | 投稿日: 2025-08-12

@josharian

新しい変数を作成するので、newOf(v) または varOf(v) と呼ぶでしょう。

おっしゃることはわかります。しかし、実際にはこれを定数以外で使う理由はあるのでしょうか?

はい。前にも述べたように、私はこの使い方をよくします。例えば、ポインタ値 x のシャローコピーを作るには:

x = ref(*x)

もう一つのよくあるイディオム:非ポインタ値から構造体のポインタ値フィールドを設定する場合:

foo := &T{
    field: ref(someValue),
}

ちなみに、これについてもっと考えてみると、expr が型でない場合の new(expr) に反対しないかもしれません。Go は常に型と変数を同じ名前空間に持つことになりますし、前例はないものの、単一の組み込み関数呼び出しで両者を混在させることはおそらく問題ないでしょう。

ということで、今のところそれが私の意見です。

コメント 243

投稿者: aclements | 投稿日: 2025-08-15

提案委員会は new(expr) に賛成しています。

主な欠点(両方とも関連していますが)は、new(T)new(expr) の間の異なる動作、および2つの形式間の構文的曖昧さです。しかし、動作の違いはかなり軽微です。両方の形式とも新しいストレージを返すからです(& のバリエーションははるかに統一性に欠けていました)。違いは初期化の方法だけです。構文的曖昧さもかなり軽微に思えます。曖昧な式の形式は sympkg.sym だけです。型リテラルである式は明らかに型であり、それ以外の式は明らかに値です。

動機については、Go 言語委員会がすでにそれを確立しており、@adonovan が収集したデータは、これがジェネリック関数として書けるとはいえ、あまりに多くのインスタンスがあるため、標準化された組み込み関数にする価値が十分にあることを示しています。

new を使うか新しい組み込み名を使うかについては、新しい組み込み関数のほうが負担が大きく、new を避ける唯一の理由は曖昧さですが、それは軽微だと考えています。曖昧さを除けば、new はこれに対して断然最適な名前に思えます。

代入コンテキストから型なし式の型を推論することについては、これは型なし式が現在動作する方法からの重大な逸脱になります。将来的にこれをサポートすることを再検討することは可能であり、それは後方互換性のある変更になるでしょう。特に、Go が最終的に戻り値型推論をサポートするようになれば、new にも同じメカニズムを使うのが理にかなうでしょう。

コメント 244

投稿者: DmitriyMV | 投稿日: 2025-08-15

@aclements

では、以下のうちどれが動作しますか?全部/一部/どれも動作しない?

go
a := new(10)
var b int32 = new(10)
func(b *int64) { ... }(new(10))

コメント 245

投稿者: Merovius | 投稿日: 2025-08-15

@DmitriyMV 最後の段落の理解としては、最初のものだけが動作し、*int を生成します。残りは次のように書く必要があります:

go
var b = new(int32(10))
func(b *int64) { ... }(new(int64(10)))

コメント 246

投稿者: jakebailey | 投稿日: 2025-08-15

最初はそれに違和感がありましたが、試してみたところ func ptrTo[T any](v T) *T { return &v } も最後の2つの位置で失敗することがわかりました。面白い!

両者が一致するということは、new が今日 ptrTo が動作するすべての場所で動作することを意味するといいのですが。(ただし、ptrTo には推論が失敗した場合に型を指定できる場所があるという利点があります。)

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

コメント 247

投稿者: Merovius | 投稿日: 2025-08-16

@jakebailey 議論されたように、new(T(expr))new[T](expr) とまったく同じ文字数です。

コメント 248

投稿者: jakebailey | 投稿日: 2025-08-16

ああ、そうですね、当然でした。

コメント 249

投稿者: aclements | 投稿日: 2025-08-20

では、以下のうちどれが動作しますか?

a := new(10) var b int32 = new(10) func(b *int64) { ... }(new(10))

これを補足すると、Go は一般的なルールとして、型が離れた場所にある場合は 2箇所 に型を記述することを要求します。これは意図的にプログラマに両方の場所で型を認識させ、コンパイラがそれらの一致を確認します。

他の方々が述べたように、a := new(10) は問題なく動作します。2番目の例は var b = new(int32(10))(または b := new(int32(10)))と書くことができます。その場合、型の「ソース」と「シンク」は構文的に非常に近いので、型は一度だけ記述すれば済みます。ただし、左側ではなく右側に書く必要があります。3番目の例は、ソースとシンクが通常離れている例であるため、型を2回記述する必要があります。(型なし定数がこの一般的なルールの例外であることは事実です。)

コメント 250

投稿者: adonovan | 投稿日: 2025-09-03

https://go.dev/ref/spec#Allocation セクションの新しい仕様文言の提案:

Allocation

組み込み関数 new は新しい初期化された変数を作成し、そのポインタを返します。単一の引数を受け取り、それは式または型のいずれかです。

引数 expr が型 T の式、または デフォルト型 が T の定数式である場合、new(expr) は型 T の変数を割り当て、expr の値で初期化し、そのアドレス(型 *T の値)を返します。

引数が型 T の場合、new(T) は型 T の ゼロ値 で初期化された変数を割り当てます。

例えば、new(123)new(int) はそれぞれ型 int の新しい変数へのポインタを返します。最初の変数の値は 123 で、2番目の変数の値は 0 です。

cc: @robpike


コメント 251

投稿者: gazerro | 投稿日: 2025-09-03

@adonovan あなたが書いた:

"… or a constant expression whose default type is T …"

より正確には、こう言うべきです:

"… or an untyped constant expression whose default type is T …"

コメント 252

投稿者: aclements | 投稿日: 2025-09-10

上記の議論に基づき、この提案は likely accept(採用の可能性が高い) と判断されます。 — aclements(提案レビューグループを代表して)

この提案は、https://go.dev/ref/spec#Allocation セクションを以下のように修正することで、new(expr) のサポートを追加するものです:

Allocation

組み込み関数 new は、新しい初期化された変数を作成し、そのポインタを返します。単一の引数を受け取り、それは式または型のいずれかです。

引数 expr が型 T の式、または デフォルト型 が T である型なし定数式である場合、new(expr) は型 T の変数を割り当て、expr の値で初期化し、そのアドレス(型 *T の値)を返します。

引数が型 T である場合、new(T) は型 T の ゼロ値 で初期化された変数を割り当てます。

例えば、new(123)new(int) はそれぞれ int 型の新しい変数へのポインタを返します。最初の変数の値は 123 で、2番目の変数の値は 0 です。

コメント 253

投稿者: robpike | 投稿日: 2025-09-11

この議論の中で何度か説明したように、型を明示的にする、つまり式はオプションの第二引数とする方が望ましいと考えていましたが、この提案が合意に達しつつあり、最終版が長年の問題を解決するものである以上、喜ぶほかありません。ありがとうございます。

コメント 254

投稿者: aclements | 投稿日: 2025-09-17

合意に変更はないため、accepted(採用) となりました。 この issue は今後、提案の実装作業を追跡するものとなります。 — aclements(提案レビューグループを代表して)

この提案は、https://go.dev/ref/spec#Allocation セクションを以下のように修正することで、new(expr) のサポートを追加するものです:

Allocation

組み込み関数 new は、新しい初期化された変数を作成し、そのポインタを返します。単一の引数を受け取り、それは式または型のいずれかです。

引数 expr が型 T の式、または デフォルト型 が T である型なし定数式である場合、new(expr) は型 T の変数を割り当て、expr の値で初期化し、そのアドレス(型 *T の値)を返します。

引数が型 T である場合、new(T) は型 T の ゼロ値 で初期化された変数を割り当てます。

例えば、new(123)new(int) はそれぞれ int 型の新しい変数へのポインタを返します。最初の変数の値は 123 で、2番目の変数の値は 0 です。

コメント 255

投稿者: gopherbot | 投稿日: 2025-09-18

変更 https://go.dev/cl/704935 がこの issue に言及しています: {go/types,cmd/compile/internal/types2}: allow new(expr)

コメント 256

投稿者: gopherbot | 投稿日: 2025-09-18

変更 https://go.dev/cl/704737 がこの issue に言及しています: doc/go_spec.html: document new(expr)

コメント 257

投稿者: gopherbot | 投稿日: 2025-09-18

変更 https://go.dev/cl/704955 がこの issue に言及しています: go/ssa: support new(expr)

コメント 258

投稿者: gopherbot | 投稿日: 2025-09-18

変更 https://go.dev/cl/705157 がこの issue に言及しています: cmd/compile/internal/noder: support new(expr)

コメント 259

投稿者: gopherbot | 投稿日: 2025-09-22

変更 https://go.dev/cl/704820 がこの issue に言及しています: gopls/internal/analysis/modernize: pass to use go1.26 new(x)

コメント 260

投稿者: gopherbot | 投稿日: 2025-09-23

変更 https://go.dev/cl/706255 がこの issue に言及しています: x/tools: fix remaining places in preparation for new(expr)

コメント 261

投稿者: gopherbot | 投稿日: 2025-09-25

変更 https://go.dev/cl/706735 がこの issue に言及しています: cmd: update x/tools@4df13e3

コメント 262

投稿者: gopherbot | 投稿日: 2025-10-20

変更 https://go.dev/cl/713241 がこの issue に言及しています: go/types, types2: only report version errors if new(expr) is ok otherwise

コメント 263

投稿者: gopherbot | 投稿日: 2025-10-22

変更 https://go.dev/cl/714040 がこの issue に言及しています: doc/next: improve new(expr) release note

コメント 264

投稿者: adonovan | 投稿日: 2025-10-27

すべて完了しました。#9097 からわずか11年しか経っていませんね。😉

コメント 265

投稿者: gopherbot | 投稿日: 2025-10-30

変更 https://go.dev/cl/716561 がこの issue に言及しています: go/analysis/passes/modernize: newexpr: add //go:fix inline directives

コメント 266

投稿者: arvenil | 投稿日: 2026-02-11

こんにちは、

変更 go.dev/cl/704737 がこの issue に言及しています: doc/go_spec.html: document new(expr)

この機能は、encoding/json や protocol buffers のような、オプションの値を表すためにポインタを使用するシリアライゼーションパッケージを扱う際に特に有用です。

素朴な疑問ですが、なぜこの例では json シリアライゼーションパッケージがオプションの値を表すためにポインタを使用することに固執しているのでしょうか?オプションの値は omitzero でも実現でき、ポインタを完全に排除できるようになったのに。

コメント 267

投稿者: swtch1 | 投稿日: 2026-02-11

素朴な疑問ですが、なぜこの例では json シリアライゼーションパッケージがオプションの値を表すためにポインタを使用することに固執しているのでしょうか?オプションの値は omitzero でも実現でき、ポインタを完全に排除できるようになったのに。

なぜなら、0 は「未指定」とは異なるからです。簡単な例を挙げましょう。プログラムが「ゲージを設定する値を指定してください」と言ったとします。

  • 1 は値を 1 に設定する
  • 0 は値を 0 に設定する
  • Nil は値を変更しない

コメント 268

投稿者: adonovan | 投稿日: 2026-02-11

素朴な疑問ですが、なぜこの例では json シリアライゼーションパッケージがオプションの値を表すためにポインタを使用することに固執しているのでしょうか?オプションの値は omitzero でも実現でき、ポインタを完全に排除できるようになったのに。

答えは例の中にあります:

go
type Person struct {
	Name string   `json:"name"`
	Age  *int     `json:"age"` // age if known; nil otherwise
}

新生児の Age はゼロです。Age が nil というのは、その Person が年齢の公開を拒否したことを意味します。omitzero タグを使うと、赤ちゃんの既知の年齢であるゼロがマーシャリングとアンマーシャリングの際に失われてしまいます。私たちが求めているのは、フィールドの nil かどうかが保持され、nil と「ゼロを指す非 nil ポインタ」を区別できることです。

コメント 269

投稿者: Merovius | 投稿日: 2026-02-11

参考までに、@arvenil は以下のようなものを指していると思いました。

go
type Optional[T any] struct {
    Value T
    Valid bool
}

func (o *Optional[T]) UnmarshalJSON(b []byte) error {
    if err := json.Unmarshal(&o.Value); err != nil {
        return err
    }
    o.Valid = true
    return nil
}

そして、任意の Optional[T] フィールドに json:"omitzero" を追加するということです。以前はこれが機能しませんでした。フィールドを省略されたフィールドとしてマーシャルする方法がなかったからです。

とはいえ、

[…] なぜこの例では json シリアライゼーションパッケージがオプションの値を表すためにポインタを使用することに固執しているのか […]

何かに固執しているわけではないと思います。オプションの値にポインタを使いたくなければ、使わなくて構いません。しかし経験的に、多くのパッケージがポインタを使用しており、そのような場合に new(expr) は便利です。これは単なる例であり、それ以上の意味を読み取る必要はありません。

コメント 270

投稿者: siutsin | 投稿日: 2026-02-18

https://cs.opensource.google/go/go/+/refs/tags/go1.26.0:src/builtin/builtin.go;l=223-226 では、まだ以下のように記載されています:

// The new built-in function allocates memory. The first argument is a type,
// not a value, and the value returned is a pointer to a newly
// allocated zero value of that type.
func new(Type) *Type

コメント 271

投稿者: gopherbot | 投稿日: 2026-02-18

変更 https://go.dev/cl/746561 がこの issue に言及しています: builtin: update new function comment

コメント 272

投稿者: gopherbot | 投稿日: 2026-02-18

変更 https://go.dev/cl/746481 がこの issue に言及しています: builtin: document new(T)

コメント 273

投稿者: gopherbot | 投稿日: 2026-02-18

変更 https://go.dev/cl/746481 がこの issue に言及しています: builtin: document new(V)

コメント 274

投稿者: aclements | 投稿日: 2026-02-20

報告の締めくくりとして:#77584 が builtin パッケージの誤ったドキュメントを追跡しています。指摘してくれてありがとうございます、@siutsin!


関連Issue