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

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

このコミットは、Go言語のランタイムにおけるマップ操作の挙動変更に関するものです。具体的には、nilマップに対して要素の削除(delete関数)を行った際の挙動を、パニック(panic)から何もしない(no-op)に変更しています。これにより、nilマップに対する削除操作がより安全かつ予測可能になります。

コミット

commit 28a50c7f51ea031f91b47421322be981a5a0d8a6
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Thu Dec 13 23:48:48 2012 +0800

    runtime: deletion on nil maps is a no-op now
    Fixes #4535.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/6942044

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

https://github.com/golang/go/commit/28a50c7f51ea031f91b47421322be981a5a0d8a6

元コミット内容

runtime: deletion on nil maps is a no-op now

このコミットは、nilマップに対する要素の削除操作が、パニックを引き起こす代わりに何もしない(no-op)ように変更することを目的としています。これは、Go言語のIssue #4535を修正するものです。

変更の背景

Go言語において、マップ(map)は参照型であり、初期化されていないマップ変数はnil値を取ります。Go 1.0の時点では、nilマップからの要素の読み取りや、nilマップへの要素の追加はパニックを引き起こしました。しかし、nilマップからの要素の削除(delete組み込み関数を使用)もまたパニックを引き起こす挙動でした。

この挙動は、特にマップがオプションである場合や、関数がnilマップを受け取る可能性がある場合に、開発者にとって不便であり、予期せぬクラッシュの原因となる可能性がありました。例えば、マップが空であるかどうかにかかわらず、特定のキーを確実に削除したい場合、nilチェックを毎回行う必要がありました。

このコミットは、nilマップに対するdelete操作がパニックを引き起こすという既存の挙動を、何もしない(no-op)というより寛容な挙動に変更することで、この問題を解決しようとしています。これにより、開発者はdelete(m, key)を呼び出す前にm != nilのチェックを行う必要がなくなり、コードの記述が簡潔になります。この変更は、Go言語の設計哲学である「実用性」と「簡潔さ」に沿ったものです。

前提知識の解説

Go言語のマップ (map)

Go言語のマップは、キーと値のペアを格納するための組み込みのデータ構造です。他の言語のハッシュマップ、ハッシュテーブル、辞書に相当します。

  • 宣言と初期化:
    var m map[string]int // 宣言のみ。m は nil マップ
    m = make(map[string]int) // make を使って初期化
    
  • nilマップ: var m map[KeyType]ValueType のように宣言されたマップは、初期値としてnilを持ちます。nilマップは、キーと値のペアを保持するためのメモリが割り当てられていない状態です。
    • nilマップからの読み取り: ゼロ値を返します(パニックは起こしません)。
    • nilマップへの書き込み: パニックを引き起こします。
    • nilマップからの削除(このコミット以前): パニックを引き起こしました。
    • nilマップの長さ(len(m)): 0を返します(パニックは起こしません)。
  • delete組み込み関数: delete(m, key) は、マップmから指定されたkeyとその値のペアを削除するために使用されます。キーが存在しない場合でも、エラーは発生せず、何もしません。

Goランタイム

Goプログラムは、Goランタイムと呼ばれる軽量な実行環境上で動作します。ランタイムは、ガベージコレクション、スケジューリング、マップやスライスなどの組み込み型の実装など、多くの低レベルな機能を提供します。このコミットで変更されているsrc/pkg/runtime/hashmap.cは、Goのマップの内部実装の一部であり、C言語で書かれています。

技術的詳細

このコミットの技術的詳細の中心は、Goランタイム内のマップ削除処理を司るruntime·mapdelete関数の変更です。

変更前は、runtime·mapdelete関数内で、引数として渡されたマップのハッシュテーブルポインタhnilである場合に、runtime·panicstring("deletion of entry in nil map")を呼び出してパニックを発生させていました。

変更後は、このパニックを発生させるコードが削除され、代わりにreturn;が追加されています。これにより、hnil(つまり、nilマップ)の場合、関数は即座に処理を終了し、何もせずに呼び出し元に戻ります。結果として、nilマップに対する削除操作はパニックを引き起こさず、何もしない(no-op)挙動となります。

この変更は、Go言語のユーザーがdelete関数をより柔軟に利用できるようにするためのものです。例えば、以下のようなコードがパニックを起こさなくなります。

var m map[string]int // m は nil
delete(m, "some_key") // この行はパニックを起こさなくなる

テストファイルtest/fixedbugs/issue4535.goが追加されており、この変更が意図通りに機能することを確認しています。このテストは、nilマップを宣言し、そのマップに対してdelete操作を実行するだけのシンプルなものです。このテストがパニックを起こさずに正常に実行されれば、変更が成功したことを意味します。

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

src/pkg/runtime/hashmap.c ファイルの runtime·mapdelete 関数内の変更です。

--- a/src/pkg/runtime/hashmap.c
+++ b/src/pkg/runtime/hashmap.c
@@ -989,7 +989,7 @@ runtime·mapdelete(MapType *t, Hmap *h, ...)
  	byte *ak;
 
  	if(h == nil)
- 		runtime·panicstring("deletion of entry in nil map");
+ 		return;
 
  	if(raceenabled)
  		runtime·racewritepc(h, runtime·getcallerpc(&t), runtime·mapdelete);

また、この変更を検証するための新しいテストファイルが追加されています。

--- /dev/null
+++ b/test/fixedbugs/issue4535.go
@@ -0,0 +1,12 @@
+// run
+
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+func main() {
+	var m map[int]int
+	delete(m, 0)
+}

コアとなるコードの解説

src/pkg/runtime/hashmap.c の変更

runtime·mapdelete 関数は、Go言語のdelete組み込み関数がマップから要素を削除する際に内部的に呼び出されるランタイム関数です。

  • if(h == nil): この条件は、削除操作の対象となるマップのハッシュテーブルポインタhnilであるかどうかをチェックしています。Goのマップ変数がnilである場合、その内部的なハッシュテーブルポインタもnilになります。
  • 変更前: runtime·panicstring("deletion of entry in nil map"); hnilの場合、この行が実行され、「deletion of entry in nil map」というメッセージと共にパニックが発生していました。
  • 変更後: return; hnilの場合、パニックを発生させる代わりに、関数は単にreturnし、処理を終了します。これにより、nilマップに対する削除操作はエラーにならず、何もしない挙動となります。

test/fixedbugs/issue4535.go の追加

このテストファイルは、変更された挙動を検証するために作成されました。

  • package main: 実行可能なプログラムであることを示します。
  • func main() { ... }: プログラムのエントリポイントです。
  • var m map[int]int: int型のキーとint型の値を持つマップmを宣言しています。この時点ではmは初期化されていないため、nilマップです。
  • delete(m, 0): nilマップmからキー0を削除しようとしています。このコミットの変更により、この行はパニックを起こさずに正常に実行されるはずです。

このテストは、// runというコメントが付いていることから、Goのテストフレームワークによって実行され、パニックが発生しないことを確認するものです。もしパニックが発生すれば、テストは失敗します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント(マップに関するセクション)
  • Go言語のソースコード(src/pkg/runtime/hashmap.c
  • Go言語のIssueトラッカー(Issue #4535に関する議論、もし見つかれば)
    • (注: 今回のWeb検索では直接的なIssue #4535のページは見つかりませんでしたが、コミットメッセージに記載されているため、過去に存在した問題であると推測されます。)
  • Go言語のリリースノート(該当するバージョンでのマップの挙動変更に関する記述)