Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 14630] ファイルの概要

このコミットは、Go言語の仕様変更に関するもので、delete 関数が nil マップに対して呼び出された際の挙動を変更しています。具体的には、以前はランタイムパニックを引き起こしていたこの操作が、変更後は何もしない(no-op)となるように修正されました。この変更は後方互換性がありますが、nil マップに対する delete 呼び出しがパニックを起こすことに依存していた(可能性は低い)コードには影響を与える可能性があります。

コミット

commit a9a49fe9625194395d9eb791de7c2674d7b6f8d8
Author: Robert Griesemer <gri@golang.org>
Date:   Wed Dec 12 13:08:35 2012 -0800

    spec: calling delete on a nil map is a no-op
    
    This is language change. It is a backward-compatible
    change but for code that relies on a run-time panic
    when calling delete on a nil map (unlikely).
    
    Fixes #4253.
    
    R=rsc, r, iant, ken, bradfitz, rogpeppe
    CC=golang-dev
    https://golang.org/cl/6909060

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/a9a49fe9625194395d9eb791de7c2674d7b6f8d8

元コミット内容

spec: calling delete on a nil map is a no-op

これは言語仕様の変更です。後方互換性のある変更ですが、nil マップに対して delete を呼び出した際にランタイムパニックに依存していた(可能性は低い)コードには影響します。

Issue #4253 を修正します。

レビュー担当者: rsc, r, iant, ken, bradfitz, rogpeppe CC: golang-dev 変更リスト: https://golang.org/cl/6909060

変更の背景

この変更の背景には、Go言語におけるマップ(map)の操作、特に nil マップの扱いに関する一貫性と堅牢性の向上が挙げられます。Go言語では、マップは参照型であり、初期化されていないマップ変数は nil 値を持ちます。nil マップは、要素を格納するためのメモリが割り当てられていない状態を意味します。

Go 1.1以前のバージョンでは、nil マップに対して delete 関数を呼び出すと、ランタイムパニック(runtime panic)が発生していました。これは、プログラムが予期せぬエラー状態に陥り、通常はクラッシュにつながる挙動です。しかし、マップから要素を読み取る操作(例: _ = m[key])や、len(m) のような操作は、nil マップに対してもパニックを起こさずに 0 やゼロ値を返すように設計されていました。

このような挙動の不一致は、開発者にとって混乱の原因となる可能性がありました。特に、マップが nil であるかどうかを事前にチェックせずに delete を呼び出すようなコードは、意図せずパニックを引き起こすリスクがありました。Issue #4253 は、この delete 関数の挙動の不一致を指摘し、nil マップに対する delete 呼び出しも他の安全な操作と同様にパニックを起こさず、何もしない(no-op)ように変更することを提案しました。

この変更により、Go言語のマップ操作のセマンティクスがより一貫性のあるものになり、開発者は delete を呼び出す前に nil チェックを行う必要がなくなるため、コードの記述がより簡潔かつ安全になります。これは、Go言語が堅牢で実用的なプログラミング言語を目指す上で重要な改善点でした。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念と用語に関する知識が必要です。

  1. マップ (Map):

    • Go言語におけるマップは、キーと値のペアを格納するための組み込みのデータ構造です。他の言語ではハッシュマップ、ハッシュテーブル、連想配列などと呼ばれることもあります。
    • マップは make 関数を使って初期化するか、マップリテラルを使って宣言・初期化します。例: m := make(map[string]int) または m := map[string]int{"a": 1, "b": 2}
    • マップのキーは比較可能でなければならず(例: 整数、文字列、ポインタ、構造体など)、値は任意の型にできます。
  2. nil:

    • Go言語では、ポインタ、スライス、マップ、チャネル、関数、インターフェースなどの参照型は、初期値として nil を取ることができます。
    • nil は「ゼロ値」の一種であり、その型の有効な値がまだ割り当てられていないことを示します。
    • nil マップは、メモリが割り当てられておらず、要素を格納できない状態のマップです。
  3. delete 関数:

    • delete はGo言語の組み込み関数で、マップから指定されたキーとその値のペアを削除するために使用されます。
    • 構文は delete(m, k) で、m はマップ、k は削除したいキーです。
    • もし指定されたキーがマップに存在しない場合でも、delete はパニックを起こさず、何もしません(no-op)。
  4. ランタイムパニック (Runtime Panic):

    • パニックは、Goプログラムが回復不能なエラー状態に遭遇したときに発生します。これは通常、プログラムの異常終了につながります。
    • 例えば、nil ポインタのデリファレンス、配列の範囲外アクセス、nil マップへの要素の追加(このコミット以前の nil マップからの削除も含む)などがパニックの原因となります。
    • パニックは recover 関数を使って捕捉することも可能ですが、通常はプログラムの論理的な欠陥を示すものです。
  5. 後方互換性 (Backward Compatibility):

    • ソフトウェアやシステムが、以前のバージョンで作成されたデータやコードと互換性があることを指します。
    • このコミットの変更は「後方互換性がある」とされています。これは、既存のGoプログラムがこの変更によって壊れる可能性が低いことを意味します。ただし、nil マップに対する delete がパニックを起こすことに「依存していた」ごく稀なケースでは影響がある、と注記されています。

これらの概念を理解することで、nil マップに対する delete の挙動変更がGo言語の設計思想とどのように関連しているか、そしてなぜこの変更が重要であるかが明確になります。

技術的詳細

このコミットの技術的詳細は、Go言語のランタイムシステムがマップ操作をどのように処理するか、特に nil マップのケースに焦点を当てています。

Go言語のマップは、内部的にはハッシュテーブルとして実装されています。マップ変数が宣言された直後、または var m map[K]V のように初期化された場合、その値は nil です。nil マップは、基盤となるハッシュテーブルのデータ構造へのポインタが nil である状態を意味します。

Goのランタイムは、マップ操作(読み取り、書き込み、削除、長さの取得など)を行う際に、まずマップ変数が nil でないかを確認します。

  • nil マップからの読み取り (v, ok := m[key]):

    • nil マップから要素を読み取ろうとすると、パニックは発生しません。代わりに、v には値の型のゼロ値が、ok には false が返されます。これは、そのキーが存在しないことを示します。
  • nil マップへの書き込み (m[key] = value):

    • nil マップに要素を書き込もうとすると、ランタイムパニックが発生します。これは、要素を格納するためのメモリが割り当てられていないためです。マップに要素を追加するには、make を使ってマップを初期化する必要があります。
  • len(m):

    • nil マップの長さを取得しようとすると、パニックは発生せず、0 が返されます。
  • delete(m, key) (変更前):

    • このコミット以前は、nil マップに対して delete 関数を呼び出すと、ランタイムパニックが発生していました。これは、マップの内部構造を操作しようとする際に、基盤となるデータ構造が存在しないためでした。
  • delete(m, key) (変更後):

    • このコミットによって、delete 関数は nil マップが渡された場合でもパニックを起こさなくなりました。代わりに、delete 関数は単に何もしない(no-op)で処理を終了します。これは、delete の実装が、キーが存在しない場合と同様に、マップが nil である場合も安全に処理するように変更されたことを意味します。
    • この変更は、Go言語の仕様書 doc/go_spec.html の該当箇所を更新することで反映されています。

この変更の技術的な影響は、Goコンパイラとランタイムが delete 組み込み関数の呼び出しを処理する方法にあります。コンパイラは delete 呼び出しを特定のランタイム関数(例: runtime.mapdelete)に変換しますが、このランタイム関数が nil マップを安全に処理するように内部ロジックが更新されたと考えられます。これにより、開発者は delete を使用する際に nil マップの可能性を考慮する必要が少なくなり、コードの堅牢性が向上します。

コアとなるコードの変更箇所

このコミットによるコアとなるコードの変更箇所は、Go言語の仕様書である doc/go_spec.html ファイル内の記述です。実際のGoランタイムのコード(CやGoで書かれた内部実装)が変更されたことを示唆していますが、このコミット自体は仕様書の更新に限定されています。

--- a/doc/go_spec.html
+++ b/doc/go_spec.html
@@ -5104,9 +5104,8 @@ delete(m, k)  // remove element m[k] from map m
 </pre>
  
  <p>
-If the element <code>m[k]</code> does not exist, <code>delete</code> is
-a no-op. Calling <code>delete</code> with a nil map causes a
-<a href="#Run_time_panics">run-time panic</a>.
+If the map <code>m</code> is <code>nil</code> or the element <code>m[k]</code>
+does not exist, <code>delete</code> is a no-op.
 </p>
  
  

変更点は以下の通りです。

  • 削除された行:

    If the element <code>m[k]</code> does not exist, <code>delete</code> is
    a no-op. Calling <code>delete</code> with a nil map causes a
    <a href="#Run_time_panics">run-time panic</a>.
    

    この行は、delete がキーが存在しない場合は no-op であることと、nil マップに対して呼び出された場合はランタイムパニックを引き起こすことを明記していました。

  • 追加された行:

    If the map <code>m</code> is <code>nil</code> or the element <code>m[k]</code>
    does not exist, <code>delete</code> is a no-op.
    

    この行は、マップ mnil である場合、または 要素 m[k] が存在しない場合、delete は no-op であると変更しています。これにより、nil マップに対する delete がパニックを起こさないという新しい挙動が公式に文書化されました。

コアとなるコードの解説

このコミットの「コアとなるコードの変更箇所」は、Go言語の公式仕様書である doc/go_spec.html の記述です。これは、Go言語の挙動を定義する最も権威あるドキュメントであり、このドキュメントの変更は、言語のセマンティクスそのものが変更されたことを意味します。

変更前は、仕様書には以下のように記述されていました。 「要素 m[k] が存在しない場合、delete は何もしない。nil マップに対して delete を呼び出すと、ランタイムパニックを引き起こす。」

この記述は、nil マップに対する delete がパニックを引き起こすという、当時のGo言語の実際の挙動を正確に反映していました。

変更後、仕様書は以下のように変更されました。 「マップ mnil である場合、または要素 m[k] が存在しない場合、delete は何もしない。」

この新しい記述は、delete 関数が nil マップに対して呼び出された場合でも、キーが存在しない場合と同様に、安全に何もしない(no-op)で処理を終えることを明確にしています。これにより、Go言語のマップ操作における一貫性が向上し、開発者は delete を使用する際に nil マップの可能性を特別に考慮する必要がなくなりました。

この仕様変更は、Goコンパイラとランタイムの内部実装が、この新しい挙動に準拠するように更新されたことを前提としています。つまり、このHTMLファイルの変更は、Go言語のコアな動作原理が変更されたことの「証拠」であり、実際の動作変更はGoのランタイムコードベースで行われています。しかし、このコミット自体は、その変更を公式に文書化したものとして非常に重要です。

関連リンク

参考にした情報源リンク