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

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

コミット

コミットハッシュ: 5fc3771b3a052ad8b724d6c5f6d491aa5a4b88b3 コミットタイトル: gc: remove m[k] = x, false 作成者: Russ Cox rsc@golang.org 作成日: 2011年11月11日 16:48:25 EST レビュー: R=ken2, CC=golang-dev Code Review URL: https://golang.org/cl/5376076

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

https://github.com/golang/go/commit/3f4a91d778ac4cab817e9d08c193a00a642f19aa

元コミット内容

このコミットは、Go言語のマップ操作における特殊な構文 m[k] = x, false を完全に削除する大規模な変更です。変更されたファイルは以下の通りです:

  • src/cmd/gc/esc.c: エスケープ解析からOAS2MAPW操作を削除
  • src/cmd/gc/fmt.c: フォーマット処理からOAS2MAPW操作を削除
  • src/cmd/gc/go.h: 列挙型定義からOAS2MAPW定数を削除
  • src/cmd/gc/typecheck.c: タイプチェック処理の大幅な変更
  • src/cmd/gc/walk.c: ASTウォーク処理からOAS2MAPW操作を削除
  • src/pkg/runtime/hashmap.c: runtime·mapassign2関数の完全削除
  • test/fixedbugs/bug220.go: テストファイルの完全削除
  • test/fixedbugs/bug242.go: テストケースの修正
  • test/named1.go: エラーテストケースの削除

統計: 9ファイルで5行追加、71行削除

変更の背景

2011年当時、Go言語はまだ1.0リリース前の開発段階にあり、言語仕様の大幅な変更が頻繁に行われていました。この時期は、Go言語の設計者たちが言語の一貫性とシンプルさを追求し、冗長や混乱を招く可能性のある機能を積極的に削除していた時期でもありました。

m[k] = x, false という構文は、マップからエントリを削除するための特殊な記法でした。しかし、この構文は以下の問題を抱えていました:

  1. 直感的でない: 削除操作が代入操作のように見える
  2. 一貫性の欠如: 他の操作と比べて特殊すぎる
  3. 可読性の低下: コードの意図が明確でない
  4. 実装の複雑化: コンパイラ内部で特別な処理が必要

Go言語の設計哲学である「シンプルで明確な構文」に反するこの機能は、より明確な delete(m, k) 関数に置き換えられることになりました。

前提知識の解説

Go言語のマップ操作

Go言語のマップ(map)は、キーと値のペアを格納するデータ構造です。基本的な操作は以下の通りです:

// マップの作成
m := make(map[string]int)

// 値の設定
m["key"] = 42

// 値の取得
value := m["key"]

// 値の取得(存在確認付き)
value, ok := m["key"]

// 値の削除(現在の方法)
delete(m, "key")

削除された構文の仕組み

削除された m[k] = x, false 構文は、以下のような動作をしていました:

// 削除された構文(2011年以前)
m[k] = value, false  // キーkを削除

// 通常の代入
m[k] = value, true   // キーkに値を設定(trueは省略可能)

この構文では、右辺の第二引数が false の場合、実際には削除操作が実行されていました。

コンパイラ内部の処理

Go言語のコンパイラ(gc)は、ソースコードを抽象構文木(AST)に変換し、各ノードに操作タイプを割り当てます。削除された構文は OAS2MAPW という特殊な操作タイプで処理されていました:

  • OAS2MAPW: Map Write Assignment(マップ書き込み代入)
  • OAS2MAPR: Map Read Assignment(マップ読み込み代入)
  • ODELETE: Delete Operation(削除操作)

技術的詳細

コンパイラフロントエンドの変更

タイプチェック処理の変更(typecheck.c)

最も重要な変更は typecheckas2 関数にありました:

変更前の処理:

if(cl == 1 && cr == 2 && l->op == OINDEXMAP) {
    if(l->type == T)
        goto out;
    n->op = OAS2MAPW;
    n->rlist->n = assignconv(r, l->type, "assignment");
    rr = n->rlist->next->n;
    n->rlist->next->n = assignconv(rr, types[TBOOL], "assignment");
    if(isconst(rr, CTBOOL) && !rr->val.u.bval) {
        n->op = ODELETE;
        n->list = list(list1(l->left), l->right);
        n->right = n->rlist->n;
        n->rlist = nil;
    }
    goto out;
}

変更後の処理:

if(cl == 1 && cr == 2 && l->op == OINDEXMAP) {
    if(l->type == T)
        goto out;
    yyerror("assignment count mismatch: %d = %d (use delete)", cl, cr);
    goto out;
}

この変更により、左辺が1つで右辺が2つの場合(マップのインデックス代入)は、エラーメッセージを出力して delete 関数の使用を推奨するようになりました。

ASTウォーク処理の変更(walk.c)

walkexpr 関数から OAS2MAPW ケースが完全に削除されました:

削除されたコード:

case OAS2MAPW:
    // map[] = a,b - mapassign2
    // a,b = m[i];
    *init = concat(*init, n->ninit);
    n->ninit = nil;
    walkexprlistsafe(n->list, init);
    l = n->list->n;
    t = l->left->type;
    n = mkcall1(mapfn("mapassign2", t), T, init, typename(t), l->left, l->right, n->rlist->n, n->rlist->next->n);
    goto ret;

ランタイムシステムの変更

mapassign2関数の削除(hashmap.c)

ランタイムから runtime·mapassign2 関数が完全に削除されました:

削除された関数:

void runtime·mapassign2(MapType *t, Hmap *h, ...)
{
    byte *ak, *av, *ap;

    if(h == nil)
        runtime·panicstring("assignment to entry in nil map");

    ak = (byte*)&h + h->ko2;
    av = (byte*)&h + h->vo2;
    ap = (byte*)&h + h->po2;

    if(*ap == false)
        av = nil;  // delete

    runtime·mapassign(t, h, ak, av);

    if(debug) {
        runtime·prints("mapassign2: map=");
        runtime·printpointer(h);
        runtime·prints("; key=");
        h->keyalg->print(h->keysize, ak);
        runtime·prints("\n");
    }
}

この関数は、第三引数のboolean値を検査し、false の場合は av = nil を設定して削除操作を実行していました。

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

1. 列挙型定義の変更(go.h:62-63)

// 変更前
OAS, OAS2, OAS2MAPW, OAS2FUNC, OAS2RECV, OAS2MAPR, OAS2DOTTYPE,

// 変更後
OAS, OAS2, OAS2FUNC, OAS2RECV, OAS2MAPR, OAS2DOTTYPE,

2. タイプチェック処理の簡略化(typecheck.c:84-94)

// 変更前:複雑な条件分岐とOAS2MAPW処理
n->op = OAS2MAPW;
n->rlist->n = assignconv(r, l->type, "assignment");
rr = n->rlist->next->n;
n->rlist->next->n = assignconv(rr, types[TBOOL], "assignment");
if(isconst(rr, CTBOOL) && !rr->val.u.bval) {
    n->op = ODELETE;
    n->list = list(list1(l->left), l->right);
    n->right = n->rlist->n;
    n->rlist = nil;
}

// 変更後:シンプルなエラーメッセージ
yyerror("assignment count mismatch: %d = %d (use delete)", cl, cr);

3. ランタイム関数の完全削除(hashmap.c:136-163)

28行にわたる runtime·mapassign2 関数が完全に削除されました。

コアとなるコードの解説

タイプチェック処理の変更意図

変更前のコードは、以下の複雑な処理を行っていました:

  1. 構文の検証: cl == 1 && cr == 2 && l->op == OINDEXMAP
    • 左辺が1つ、右辺が2つ、左辺がマップインデックス
  2. 操作タイプの設定: n->op = OAS2MAPW
  3. 型変換の実行: assignconv による型の適合性チェック
  4. 定数の評価: isconst(rr, CTBOOL) && !rr->val.u.bval
    • 右辺第二引数が定数falseかどうかの判定
  5. 削除操作への変換: n->op = ODELETE

これらの処理により、m[k] = x, false は内部的に delete(m, k) 操作に変換されていました。

ランタイム関数の責務

削除された runtime·mapassign2 関数の主な責務:

  1. 引数の解析: マップ、キー、値、boolean値の取得
  2. 条件分岐: boolean値による削除/代入の判定
  3. 操作の実行: runtime·mapassign への委譲
  4. デバッグ出力: デバッグモード時の情報出力

この関数の削除により、マップ操作の実装が大幅に簡素化されました。

テストケースの変更

bug242.goでは、以下のように変更されました:

// 変更前
m[gint()] = gbyte(), false

// 変更後
delete(m, gint())
gbyte()

この変更により、副作用のある関数呼び出し gbyte() が明示的に実行されるようになり、コードの動作がより明確になりました。

関連リンク

参考にした情報源リンク

この変更は、Go言語の設計哲学である「シンプルで明確な構文」を体現する重要な改善であり、言語の一貫性と可読性を大幅に向上させました。delete 関数の導入により、マップ操作がより直感的で理解しやすくなり、現在のGo言語の基礎となる重要な変更でした。