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

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

このコミットは、Go言語の公式仕様書である doc/go_spec.html ファイルに対する変更です。具体的には、Go言語には「参照型」という概念がないことを明確にし、スライスに関する記述をより正確にするための修正が含まれています。

コミット

commit b34f0551387fcf043d65cd7d96a0214956578f94
Author: Robert Griesemer <gri@golang.org>
Date:   Tue Apr 2 23:17:37 2013 -0700

    spec: Go has no 'reference types'

    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/8288044

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

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

元コミット内容

spec: Go has no 'reference types'

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/8288044

変更の背景

このコミットの背景には、Go言語の型システムに関する一般的な誤解を解消し、より正確な情報を提供しようとする意図があります。多くのプログラミング言語(Java、C#など)では、「参照型」という明確なカテゴリが存在し、変数がオブジェクトへの参照を保持するという概念が一般的です。しかし、Go言語では、ポインタ、スライス、マップ、チャネルといった型が、他の言語の参照型と似た振る舞いをすることがありますが、Goの設計思想としては「参照型」という用語を公式には使用していません。

特にスライスは、内部的に配列へのポインタ、長さ、容量を持つデータ構造であり、他の言語の配列参照や動的配列と混同されがちです。このコミット以前の仕様書では、スライスが「参照」であるという表現が使われていましたが、これはGoの型システムにおける「値渡し」の原則と矛盾する可能性があり、混乱を招く恐れがありました。

この変更は、Go言語の設計者の一人であるRobert Griesemer氏によって行われました。彼は、Goの型システムが他の言語とは異なるアプローチを取っていることを明確にし、特にスライスが「配列の連続したセグメントへの参照」ではなく、「配列の連続したセグメントのディスクリプタ(記述子)」であるという、より正確な表現に修正することで、Goのセマンティクスに対する理解を深めることを目的としています。これにより、Goのプログラムがどのようにメモリを扱い、値がどのように渡されるかについての誤解を防ぎます。

前提知識の解説

値型と参照型(一般的なプログラミング言語における概念)

  • 値型 (Value Type): 変数自体がデータの実体を保持する型です。変数を別の変数に代入すると、データのコピーが作成されます。例えば、C言語の int やGo言語の int, struct などがこれに該当します。
  • 参照型 (Reference Type): 変数がデータの格納されているメモリ上の場所(アドレス)への「参照」を保持する型です。変数を別の変数に代入すると、参照がコピーされるだけで、データの実体はコピーされません。そのため、複数の変数が同じデータの実体を参照し、いずれかの変数を通じてデータを変更すると、他の変数からもその変更が反映されます。JavaのオブジェクトやC#のクラスインスタンスなどがこれに該当します。

Go言語における値渡しとポインタ

Go言語は基本的に「値渡し (pass by value)」のセマンティクスを採用しています。これは、関数に引数を渡す際、その引数の値がコピーされて渡されることを意味します。

しかし、Goには「ポインタ (pointer)」という概念があります。ポインタは、変数のメモリ上のアドレスを保持する型です。ポインタを介して変数にアクセスすることで、その変数の実体を変更することができます。

package main

import "fmt"

func modifyValue(x int) {
    x = 100 // xはコピーされた値なので、元の変数は変更されない
}

func modifyPointer(x *int) {
    *x = 100 // ポインタが指す先の値を変更するので、元の変数が変更される
}

func main() {
    a := 10
    modifyValue(a)
    fmt.Println(a) // 出力: 10

    b := 10
    modifyPointer(&b) // bのアドレスを渡す
    fmt.Println(b) // 出力: 100
}

Go言語のスライス、マップ、チャネルの内部構造と振る舞い

Go言語のスライス、マップ、チャネルは、他の言語の参照型と似た振る舞いをすることがありますが、これらは厳密には「参照型」とは呼ばれません。Goの設計思想では、これらは「値」として扱われますが、その「値」の内部にポインタが含まれているため、特定の操作において参照的なセマンティクスを示すように見えます。

  • スライス (Slice): スライスは、内部的に以下の3つの要素を持つ構造体です。

    1. ポインタ (Pointer): 基底となる配列の先頭要素へのポインタ。
    2. 長さ (Length): スライスが現在保持している要素の数。
    3. 容量 (Capacity): 基底となる配列の、スライスが拡張できる最大要素数。 スライスを関数に渡す際も、この3つの要素を持つ構造体全体が値としてコピーされます。しかし、コピーされたスライスのポインタは元のスライスと同じ基底配列を指しているため、コピーされたスライスを通じて基底配列の要素を変更すると、元のスライスからもその変更が見えます。これが、スライスが参照型のように振る舞うと誤解される原因です。
  • マップ (Map): マップは、内部的にハッシュテーブルへのポインタを持つ構造体です。マップを関数に渡す際も、この構造体全体が値としてコピーされますが、ポインタは同じハッシュテーブルを指しているため、マップの要素の追加、削除、変更は元のマップにも反映されます。

  • チャネル (Channel): チャネルは、内部的にチャネルのランタイムデータ構造へのポインタを持つ構造体です。チャネルを関数に渡す際も、この構造体全体が値としてコピーされますが、ポインタは同じチャネルを指しているため、チャネルを通じた通信は元のチャネルにも反映されます。

これらの型は、その内部にポインタを持つ「値」であるため、他の言語の「参照型」とは異なる概念として扱われます。Goの哲学では、明示的にポインタを使用しない限り、すべての値はコピーされるという一貫したモデルを提供しようとしています。

技術的詳細

このコミットでは、doc/go_spec.html ファイルの以下の箇所が変更されています。

  1. 仕様書のバージョン日付の更新:

    • 変更前: Version of March 22, 2013
    • 変更後: Version of April 3, 2013 これは、仕様書の内容が更新されたことを示す一般的な変更です。
  2. スライスの定義の修正:

    • 変更前: A slice is a reference to a contiguous segment of an array and contains a numbered sequence of elements from that array. (スライスは配列の連続したセグメントへの参照であり、その配列からの番号付き要素のシーケンスを含みます。)
    • 変更後: A slice is a descriptor for a contiguous segment of an array and provides access to a numbered sequence of elements from that array. (スライスは配列の連続したセグメントのディスクリプタであり、その配列からの番号付き要素のシーケンスへのアクセスを提供します。) この変更がこのコミットの核心です。「reference(参照)」という言葉が「descriptor(記述子)」に置き換えられました。これにより、スライスが単にメモリ上の位置を指すだけでなく、そのセグメントの長さや容量といった情報も含む「記述」であることを強調しています。また、「contains(含む)」から「provides access to(アクセスを提供する)」に変更することで、スライスが要素の実体を直接保持するのではなく、基底配列の要素へのアクセス手段を提供することを明確にしています。
  3. make 関数に関する記述の削除:

    • 変更前: Slices, maps and channels are reference types that do not require the extra indirection of an allocation with <code>new</code>. (スライス、マップ、チャネルは、new による割り当ての余分な間接参照を必要としない参照型です。)
    • 変更後: この行全体が削除されました。 この削除は、Goには「参照型」という概念がないというコミットの主旨を最も直接的に反映しています。以前の記述では、スライス、マップ、チャネルを「参照型」と明示的に呼んでいましたが、このコミットによってその表現が完全に排除されました。これにより、Goの型システムにおける「参照型」という用語の使用を避けるという方針が徹底されています。new 関数との関連性についても、参照型という前提がなくなったため、この説明は不要となりました。

これらの変更は、Go言語の型システムに関する公式な見解をより正確に反映し、特にスライス、マップ、チャネルの振る舞いに対する誤解を減らすことを目的としています。Goでは、これらの型は内部にポインタを持つ「値」として扱われるため、他の言語の「参照型」とは異なるセマンティクスを持つというニュアンスを強調しています。

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

--- a/doc/go_spec.html
+++ b/doc/go_spec.html
@@ -1,6 +1,6 @@
 <!--{
  	"Title": "The Go Programming Language Specification",
-	"Subtitle": "Version of March 22, 2013",
+	"Subtitle": "Version of April 3, 2013",
  	"Path": "/ref/spec"
 }-->

@@ -837,9 +837,9 @@ multi-dimensional types.
 <h3 id="Slice_types">Slice types</h3>

 <p>
-A slice is a reference to a contiguous segment of an array and
-contains a numbered sequence of elements from that array.  A slice
-type denotes the set of all slices of arrays of its element type.
+A slice is a descriptor for a contiguous segment of an array and
+provides access to a numbered sequence of elements from that array.
+A slice type denotes the set of all slices of arrays of its element type.
 The value of an uninitialized slice is <code>nil</code>.
 </p>

@@ -5197,8 +5197,6 @@ of the memory.
 <h3 id="Making_slices_maps_and_channels">Making slices, maps and channels</h3>

 <p>
-Slices, maps and channels are reference types that do not require the
-extra indirection of an allocation with <code>new</code>.
 The built-in function <code>make</code> takes a type <code>T</code>,
 which must be a slice, map or channel type,
 optionally followed by a type-specific list of expressions.

コアとなるコードの解説

上記の差分は、Go言語の仕様書 doc/go_spec.html における主要な変更点を示しています。

  1. @@ -1,6 +1,6 @@ のブロック:

    • Subtitle の日付が March 22, 2013 から April 3, 2013 に更新されています。これは、仕様書の内容がこのコミットによって更新されたことを示すメタデータ的な変更です。
  2. @@ -837,9 +837,9 @@ のブロック(スライスの定義):

    • -A slice is a reference to a contiguous segment of an array and (スライスは配列の連続したセグメントへの参照であり、)
    • -contains a numbered sequence of elements from that array. A slice (その配列からの番号付き要素のシーケンスを含みます。スライスは)
    • +A slice is a descriptor for a contiguous segment of an array and (スライスは配列の連続したセグメントのディスクリプタであり、)
    • +provides access to a numbered sequence of elements from that array. (その配列からの番号付き要素のシーケンスへのアクセスを提供します。) この部分が、スライスの定義における最も重要な変更です。 「reference(参照)」という言葉が「descriptor(記述子)」に置き換えられました。これは、Goのスライスが単なるメモリ上の参照ではなく、基底配列へのポインタ、長さ、容量という3つの要素からなる「値」であることを強調しています。 また、「contains(含む)」が「provides access to(アクセスを提供する)」に変更されたことで、スライスが要素の実体を直接保持するのではなく、基底配列の要素へのアクセス手段を提供することを明確にしています。これにより、スライスが値として渡される際に、その内部のポインタがコピーされることで、同じ基底配列を共有するというGoのセマンティクスがより正確に表現されています。
  3. @@ -5197,8 +5197,6 @@ のブロック(make 関数に関する記述):

    • -Slices, maps and channels are reference types that do not require the (スライス、マップ、チャネルは、)
    • -extra indirection of an allocation with <code>new</code>.new による割り当ての余分な間接参照を必要としない参照型です。) この2行が完全に削除されました。これは、Go言語には「参照型」という概念がないというコミットの主旨を最も直接的に反映する変更です。以前の仕様書では、スライス、マップ、チャネルを「参照型」と明示的に記述していましたが、このコミットによってその表現が完全に排除されました。これにより、Goの型システムにおける「参照型」という用語の使用を避けるという方針が徹底され、Goのセマンティクスに関する誤解を解消しようとしています。

これらの変更は、Go言語の設計思想と型システムに関する公式な見解をより正確に反映し、特にスライス、マップ、チャネルの振る舞いに対する一般的な誤解を減らすことを目的としています。

関連リンク

参考にした情報源リンク