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

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

このコミットは、Go言語のレシーバ型に関する仕様変更、または既存の仕様への準拠を目的としたものです。具体的には、レシーバ型として「名前付きポインタ型」を使用することが、当時のGo言語の仕様で禁止されていたため、その規則に違反しているコードを修正しています。test/chan/powser1.goファイル内のメソッドレシーバが、itemという名前付きポインタ型から、基底型である*ratに直接変更されています。

コミット

commit 6cd74b03f38de40d84d1d9efe8663714ccfaaee5
Author: Ian Lance Taylor <iant@golang.org>
Date:   Wed Nov 5 11:25:30 2008 -0800

    Don't use a named pointer type as a receiver type.  The
    current spec forbids it:
        The type specified by the type name is called ``receiver
        base type''.  The receiver base type must be a type
        declared in the current file, and it must not be a pointer
        type.
    
    R=r
    DELTA=2  (0 added, 0 deleted, 2 changed)
    OCL=18527
    CL=18541

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

https://github.com/golang/go/commit/6cd74b03f38de40d84d1d9efe8663714ccfaaee5

元コミット内容

レシーバ型として名前付きポインタ型を使用しない。現在の仕様ではそれが禁止されている: 「型名によって指定される型は『レシーバ基底型』と呼ばれる。レシーバ基底型は現在のファイルで宣言された型でなければならず、ポインタ型であってはならない。」

変更の背景

このコミットは、Go言語の初期の段階における言語仕様の厳密な適用を示しています。Go言語では、メソッドを定義する際にレシーバを指定しますが、そのレシーバの型には特定の制約がありました。特に、ポインタ型を直接レシーバとして使用することはできますが、ポインタ型に別名を付けた「名前付きポインタ型」をレシーバの基底型として使用することは禁止されていました。

コミットメッセージに明記されているように、当時のGo言語の仕様には以下の記述がありました。

The type specified by the type name is called ``receiver base type''. The receiver base type must be a type declared in the current file, and it must not be a pointer type.

これは、「レシーバ基底型」がポインタ型であってはならないという明確な制約を課しています。type item *rat; のように定義された item は、*rat というポインタ型に付けられた名前(名前付きポインタ型)です。したがって、item をレシーバ型として使用することは、この仕様に違反していました。

この変更は、コンパイラがこの仕様に準拠するように、または既存のコードが仕様に適合するように修正されたことを示唆しています。言語の安定性と一貫性を保つために、このような仕様違反の修正は重要です。

前提知識の解説

Go言語の型システム

Go言語は静的型付け言語であり、変数は特定の型を持ちます。型は、その変数が保持できる値の種類と、その値に対して実行できる操作を決定します。

構造体 (struct)

構造体は、異なる型のフィールドをまとめた複合データ型です。例えば、rat 構造体は numden という2つの整数フィールドを持っています。

type rat struct {
    num int;
    den int;
}

ポインタ型

ポインタは、変数のメモリアドレスを保持する特殊な型です。Goでは、*T の形式でポインタ型を宣言します。例えば、*ratrat 型の構造体へのポインタを表します。ポインタを使用することで、値のコピーを避け、元の値を直接操作することができます。

型の別名 (Type Aliases)

Go言語では、既存の型に新しい名前を付けることができます。これは type NewName OldType の形式で宣言されます。このコミットの例では、type item *rat; がこれに該当します。item*rat 型の別名です。

メソッドとレシーバ

Go言語では、関数を特定の型に関連付けることで「メソッド」を定義できます。メソッドは、その型(レシーバ)のインスタンスに対して呼び出されます。メソッドの定義は以下の形式を取ります。

func (receiver_name ReceiverType) MethodName(parameters) (return_values) {
    // ...
}

レシーバには、値レシーバとポインタレシーバの2種類があります。

  • 値レシーバ: func (u rat) pr() のように、レシーバが値型の場合。メソッド内でレシーバのフィールドを変更しても、元の値には影響しません(コピーが渡されるため)。
  • ポインタレシーバ: func (u *rat) pr() のように、レシーバがポインタ型の場合。メソッド内でレシーバのフィールドを変更すると、元の値も変更されます(アドレスが渡されるため)。

レシーバ基底型 (Receiver Base Type)

Go言語の仕様では、メソッドのレシーバ型について「レシーバ基底型」という概念が導入されています。これは、レシーバの型がポインタ型であるかどうかにかかわらず、そのレシーバが「どの型に属するメソッドであるか」を決定する基底となる型を指します。

このコミットの時点の仕様では、この「レシーバ基底型」がポインタ型であってはならないという制約がありました。つまり、type MyPointer *int のように定義された MyPointer をレシーバとして使用する場合、その基底型は *int であり、これがポインタ型であるため、仕様違反とされていました。

技術的詳細

Go言語の仕様は、言語の設計と動作を厳密に定義しています。このコミットが参照している仕様の条項は、メソッド宣言におけるレシーバの型に関するものです。

当時のGo言語の仕様(Go 1.0より前の初期のドラフト段階)では、メソッドのレシーバ型について、以下のような制約が設けられていました。

  1. レシーバ基底型は現在のファイルで宣言された型であること: これは、メソッドが定義されているファイル内で、レシーバの基底となる型が明示的に宣言されている必要があることを意味します。外部パッケージからインポートされた型や、匿名型(無名構造体など)を直接レシーバ基底型とすることはできませんでした。
  2. レシーバ基底型はポインタ型であってはならないこと: これがこのコミットの核心です。type item *rat; のように、ポインタ型に別名を付けた場合、その別名(item)の「レシーバ基底型」は、元のポインタ型である *rat と見なされます。仕様では、この基底型がポインタ型であってはならないと明記されていたため、item をレシーバとして使用することは許可されませんでした。

この制約の背景には、言語設計者がレシーバのセマンティクスを明確にし、混乱を避ける意図があったと考えられます。特に、ポインタ型に別名を付けてそれをレシーバとして使用すると、値レシーバとポインタレシーバの区別が曖昧になる可能性や、コードの可読性が低下する可能性があったのかもしれません。

現在のGo言語の仕様(Go 1以降)では、この制約は緩和されており、ポインタ型を直接レシーバとして使用することはもちろん、type MyInt *int のような名前付きポインタ型をレシーバとして使用することも可能です。しかし、このコミットが行われた2008年時点では、上記の厳格なルールが適用されていました。このコミットは、その当時の仕様にコードベースを適合させるための修正です。

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

変更は test/chan/powser1.go ファイルで行われています。

--- a/test/chan/powser1.go
+++ b/test/chan/powser1.go
@@ -19,8 +19,8 @@ type rat struct  {
 
 type item *rat;
 
-func (u item) pr(){
+func (u *rat) pr(){
 	if u.den==1 { print(u.num) }
 	else { print(u.num, "/", u.den) }
 	print(" ")
 }
 
-func (u item) eq(c item) bool {
+func (u *rat) eq(c item) bool {
 	return u.num == c.num && u.den == c.den
 }

コアとなるコードの解説

このコミットでは、test/chan/powser1.go ファイル内の2つのメソッド pr()eq() のレシーバ型が変更されています。

元のコードでは、item という名前付きポインタ型がレシーバとして使用されていました。

type item *rat; // item は *rat の別名

func (u item) pr(){ ... }
func (u item) eq(c item) bool { ... }

しかし、当時のGo言語の仕様では、「レシーバ基底型はポインタ型であってはならない」という制約がありました。item の基底型は *rat であり、これはポインタ型であるため、この定義は仕様違反と見なされました。

コミットによって、レシーバ型は item から *rat に直接変更されました。

func (u *rat) pr(){ ... }
func (u *rat) eq(c item) bool { ... } // ここは c item のままですが、レシーバは *rat に変更

この変更により、レシーバは直接ポインタ型 *rat を取るようになります。*rat はポインタ型ですが、Go言語の仕様では、ポインタ型そのものをレシーバとして使用することは許可されています。禁止されていたのは、ポインタ型に別名を付けた「名前付きポインタ型」をレシーバの「基底型」として使用することでした。

つまり、*rat はそれ自体がポインタ型であり、その「レシーバ基底型」は rat(ポインタではない型)と解釈されるため、仕様に準拠します。この修正は、当時のGo言語の厳格な型システムとレシーバの規則にコードを適合させるためのものでした。

関連リンク

  • Go言語の仕様 (The Go Programming Language Specification):
    • Go言語の仕様は頻繁に更新されるため、このコミット当時の正確な仕様を見つけるのは困難ですが、Goの公式ドキュメントは常に最新の仕様を提供しています。
    • https://go.dev/ref/spec

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語の初期のコミット履歴とメーリングリストの議論(公開されている場合)
  • Go言語のレシーバに関する一般的な解説記事