質問と背景
Go 1.26 では組み込みの new が拡張され、式を受け取って新しい変数へのポインタを返せるようになりました。API の構造体を用いてこれがオプショナルフィールドの初期化をどのように簡素化するかを示し、さらに JSON、ジェネリクス、古いコンパイラ、レビュー基準について議論してください。
面接官が評価するポイント
new(T)、アドレスの取得、および式の評価を明確に区別できているか。- ゼロ値、
omitempty、およびアンマーシャリングを伴うポインタによるオプショナル表現を説明できるか。 - Go のバージョン、ジェネリック型、可読性に関するリスクを特定できるか。
- 構文だけでなく、テストや移行計画まで提示できるか。
まず確認すべき明確化の質問
データコントラクト
API はフィールドの欠落、明示的なゼロ値、明示的な null を区別する必要がありますか?JSON クライアントは omitempty に依存していますか?データベース側にも 3 状態のセマンティクスが必要ですか?
バージョンとデプロイ環境
すべてのビルド環境が Go 1.26 に移行済みですか?共有ライブラリ、コードジェネレータ、または下流のモジュールで古いツールチェーンがまだ使われていませんか?
可読性とポリシー
チームとして、ビジネスコード内でポインタ値として複雑な式を記述することを許容していますか?デバッグやレビューのためにシンプルなヘルパーを残すべきですか?
30 秒での回答
Go 1.26 では、new(expr) を使用して式の結果を新しい変数に格納し、そのポインタを返せるようになったため、new(42) のようなオプショナルフィールドに便利です。まずはコントラクトが欠落・ゼロ値・null を区別するかどうかを判断し、ポインタまたは専用のオプショナル型を選択します。移行前にはツールチェーンを固定し、シリアライズのテストを追加し、可読性のために式を短く保ちます。
詳細な解説
1. セマンティクスの変更点を説明する
従来の new(T) は型を受け取り、そのゼロ値へのポインタを返します。Go 1.26 では式のオペランドが許可されます。コンパイラは変数を作成して式の結果を格納し、そのポインタを返します。式は通常の Go の評価規則に従うため、副作用や評価順序は明確にしておく必要があります。
2. オプショナルフィールドをモデリングする
以下の構造体は、フィールドが指定されたかどうかを表現するためにポインタを使用しています。
type CreateUser struct {
Name string `json:"name"`
Age *int `json:"age,omitempty"`
Admin *bool `json:"admin,omitempty"`
}
req := CreateUser{
Name: "Ada",
Age: new(37),
Admin: new(false),
}Admin: new(false) はセマンティクス上 Admin: nil と異なります。false が出力されるかどうかはシリアライズの規則に依存します。omitempty の下でも、ポインタが指す先がゼロ値であっても非 nil のポインタは通常そのまま出力されるため、API コントラクトをテストしてください。
3. 代替手段との比較
new(37) は短く書ける一方、age := 37; &age はデバッガでの確認が容易です。ptrToInt(37) ヘルパーを使用すれば、古いツールチェーンとの互換性を集約できます。チームのポリシー、式の複雑さ、呼び出し頻度に基づいて選択し、単に新しい構文を使うためだけにすべてのポインタ初期化を書き換えることは避けてください。
4. ジェネリクスと型推論の扱い
new(expr) の結果の型は式に従います。複雑なジェネリック式は、コードの読み手にとって型推論を難しくする可能性があります。公開ライブラリでは境界で型を明示し、内部コードでは単純な定数や名前付き変数を使用して、コンパイラや静的解析で型エラーを検出できるようにすべきです。
5. JSON およびデータベース境界への配慮
ポインタは nil と非 nil を区別しますが、データベースの NULL、空文字列、ゼロ値の間のすべての差異を解決するわけではありません。PATCH などの API では、コントラクトを明示的にテストしてください。欠落は「更新なし」、非 nil のゼロ値は「値のクリア」、null はデコード層によって受け入れられるか拒否されるかのいずれかになります。
6. 互換性と移行の計画
go.mod、CI、コンテナイメージ、コード生成ステップを Go 1.26 に揃えます。下流の利用者がまだ古いツールチェーンでコンパイルしている場合は、以前の形式を維持するか、ビルドマトリクスで新しいコードを分離します。まずは小さなパッケージから移行し、範囲を広げる前に単体テスト、シリアライズテスト、レーステストを実行します。
7. レビュー基準の設定
new には短く副作用のない式のみを許可し、複雑な計算は事前に名前付き変数にします。レビューでは、構文がコンパイルを通るかだけでなく、フィールドの 3 状態セマンティクス、エスケープ後の生存期間、エラー処理、API 互換性に焦点を当てるべきです。
優れた回答例
Go 1.26 の new(expr) は単純な式をオプショナルフィールドのポインタに変換するのに便利ですが、ポインタ、JSON、またはデータベースの 3 状態セマンティクス自体を変えるものではありません。私なら *T を用いて欠落と明示的なゼロ値を区別し、PATCH およびシリアライズのコントラクトテストを作成し、固定されたツールチェーンで移行を行います。複雑な式には名前を付け、古いツールチェーン用のパスも残すことで、可読性と互換性を計画的に選択できるようにします。
よくある間違い
- 非 nil のポインタを返すにもかかわらず、
new(0)を nil として扱う。 omitemptyがゼロ値へのポインタを自動的に除外すると誤認する。- CI や下流のツールチェーンをアップグレードする前に Go 1.26 の構文をコミットする。
- PATCH における欠落、null、ゼロ値のセマンティクスを定義せずにポインタを使用する。
- 副作用を持つ複雑な式を
newの中に記述する。 - コンパイルチェックのみを実行し、JSON やデータベース境界のテストをスキップする。
フォローアップ質問と回答
new(0) と new(int) はどう違いますか?
new(0) は値が 0 の int へのポインタを返し、new(int) は int のゼロ値へのポインタを返します。値自体は同じですが、前者は Go 1.26 の式オペランドを利用しています。
デフォルト値を持つ値フィールドを使用しないのはなぜですか?
値フィールドでは、欠落と明示的なゼロ値を区別できません。PATCH や古いクライアントとの互換性を保つには、ポインタや専用のオプショナル型のほうがコントラクトを適切に表現できます。
これはエスケープ解析に影響しますか?
変数をスタックに配置するかヒープに配置するかは、依然としてコンパイラが判断します。観測可能なセマンティクスやベンチマークに基づいて議論すべきであり、new キーワードのみからヒープ割り当てを推測してはいけません。
古い Go バージョンをどのようにサポートしますか?
モジュール、CI、リリースイメージで最小バージョンを一貫して設定します。同時アップグレードが不可能な場合は、v := value; &v またはヘルパーを維持し、ビルドマトリクスを使用して新しい構文が古いブランチに入らないようにします。
すべてのフィールドを new(expr) に切り替えるべきですか?
いいえ。オプショナルな初期化がより明確になる場合に使用してください。複雑な式、公開 API、またはデバッガのブレークポイントの恩恵を受けるコードでは、名前付き変数を使い続けるのが適切です。