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

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

このコミットは、Go言語の公式チュートリアルおよび関連するサンプルコードにおいて、配列リテラルを直接スライスする誤った記述を修正するものです。具体的には、[3]int{1,2,3}[:] のような形式がGo言語の仕様上不正であることを明確にし、一度変数に配列を代入してからスライスを行う x := [3]int{1,2,3}; s := sum(x[:]) という正しい記述に修正しています。

コミット

commit da5a251dde539c91c87caab4abe3d346f88e82fc
Author: Russ Cox <rsc@golang.org>
Date:   Fri Dec 2 12:30:37 2011 -0500

    doc: do not slice array literal
    
    The special case in the spec is that you can take the
    address of a composite literal using the & operator.
    
    A composite literal is not, however, generally addressable,
    and the slice operator requires an addressable argument,
    so [3]int{1,2,3}[:] is invalid.  This tutorial code and one bug
    report are the only places in the tree where it appears.
    
    R=r, gri
    CC=golang-dev
    https://golang.org/cl/5437120

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

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

元コミット内容

このコミットは、Go言語のドキュメントとサンプルコードにおける、配列リテラルを直接スライスする誤った記述を修正するものです。具体的には、[3]int{1,2,3}[:] のようなコードがGo言語の仕様では不正であることを指摘し、これを x := [3]int{1,2,3}; s := sum(x[:]) のように、一度配列を変数に代入してからスライスを行う形に修正しています。

コミットメッセージでは、複合リテラル(composite literal)は & 演算子を使ってアドレスを取得できる特殊なケースがあるものの、一般的にはアドレス可能(addressable)ではないと説明されています。スライス演算子([:])はアドレス可能な引数を必要とするため、[3]int{1,2,3}[:] は無効であると述べられています。この誤った記述は、チュートリアルコードと一つのバグレポートでのみ見られたとのことです。

変更の背景

Go言語の仕様において、スライス演算子([:])は、そのオペランドがアドレス可能である(つまり、メモリ上の特定の位置を指すことができる)ことを要求します。しかし、[3]int{1,2,3} のような配列リテラルは、その場で生成される一時的な値であり、通常はアドレス可能ではありません。

Goの複合リテラルは、& 演算子を前置することでそのアドレスを取得できるという特殊なルールがあります(例: &Foo{Bar: 1})。しかし、これは複合リテラル自体がアドレス可能であるという意味ではなく、& 演算子によって新しい変数が作成され、その変数がリテラルの値で初期化され、その変数のアドレスが返される、という挙動に近いものです。

したがって、[3]int{1,2,3} という配列リテラルは、それ自体がメモリ上の固定された場所を持たないため、直接スライスすることはできませんでした。このコミットは、この仕様上の制約を反映し、誤解を招く可能性のあるチュートリアルのコードを修正することで、Go言語の学習者が正しい理解を得られるようにすることを目的としています。

この修正は、Go言語の初期段階における仕様の厳密な適用と、ドキュメントの正確性を保つための継続的な努力の一環として行われました。

前提知識の解説

このコミットを理解するためには、Go言語における以下の概念を理解しておく必要があります。

1. 配列 (Arrays)

Go言語の配列は、同じ型の要素を固定長で連続して格納するデータ構造です。配列の長さは型の一部であり、コンパイル時に決定されます。 例: var a [3]int は3つの整数を格納できる配列を宣言します。

2. スライス (Slices)

スライスはGo言語の強力な機能の一つで、配列の一部を参照する動的なビューです。スライスは、基となる配列へのポインタ、長さ(len)、容量(cap)の3つの要素で構成されます。スライス自体はデータを保持せず、常に基となる配列を参照します。 スライスは array[low:high] の形式で作成されます。ここで low は開始インデックス(含む)、high は終了インデックス(含まない)です。array[:] は配列全体をスライスとして参照します。

3. 複合リテラル (Composite Literals)

複合リテラルは、配列、スライス、マップ、構造体などの複合型の値をその場で作成するための構文です。 例:

  • 配列リテラル: [3]int{1, 2, 3}
  • スライスリテラル: []int{1, 2, 3} (長さが指定されていない場合、スライスリテラルとなり、基となる配列が自動的に作成されます)
  • 構造体リテラル: Person{Name: "Alice", Age: 30}

4. アドレス可能性 (Addressability)

Go言語において、「アドレス可能(addressable)」とは、その値がメモリ上の特定の位置に存在し、そのアドレス(ポインタ)を取得できることを意味します。変数はアドレス可能です。しかし、一時的な値や定数、関数呼び出しの結果などは通常、アドレス可能ではありません。

アドレス可能な値に対しては、& 演算子を使ってそのアドレス(ポインタ)を取得できます。 例:

var x int = 10
p := &x // x はアドレス可能なので、そのアドレスを取得できる

// 以下の場合はアドレス可能ではない
// p := &10 // エラー: 10 は定数でアドレス可能ではない
// p := &(1 + 2) // エラー: (1 + 2) は一時的な値でアドレス可能ではない

5. スライス演算子の要件

Go言語の仕様では、スライス演算子 a[low:high] のオペランド a は、アドレス可能であるか、またはマップ、スライス、文字列である必要があります。配列リテラルは、それ自体がアドレス可能ではないため、直接スライス演算子のオペランドとして使用することはできません。

このコミットは、この「アドレス可能性」の概念と、スライス演算子のオペランドに対する要件が、配列リテラルにどのように適用されるかを明確にしています。

技術的詳細

このコミットの核心は、Go言語の型システムとメモリモデルにおける「アドレス可能性」の概念にあります。

Go言語の仕様では、スライス式 a[low:high] において、オペランド a は以下のいずれかの型でなければならないと規定されています。

  1. 配列 (array)
  2. 配列へのポインタ (pointer to array)
  3. スライス (slice)
  4. 文字列 (string)

さらに、a が配列である場合、その配列はアドレス可能でなければなりません。

[3]int{1,2,3} のような配列リテラルは、コンパイル時にその場で生成される一時的な値です。このような一時的な値は、通常、メモリ上の固定された場所を持たないため、アドレス可能ではありません。

Go言語の仕様には、複合リテラルに関する特別なルールがあります。それは、複合リテラルの前に & 演算子を置くことで、そのリテラルが指す型の新しい変数が作成され、その変数がリテラルの値で初期化され、その変数のアドレスが返される、というものです。 例: p := &[3]int{1,2,3} は有効です。この場合、[3]int{1,2,3} という値を持つ新しい配列変数がメモリ上に作成され、その配列へのポインタが p に代入されます。この新しい配列変数はアドレス可能です。

しかし、[3]int{1,2,3}[:] のように & 演算子なしで配列リテラルを直接スライスしようとすると、スライス演算子はアドレス可能なオペランドを期待するため、コンパイルエラーとなります。配列リテラル自体はアドレス可能ではないためです。

このコミットは、この仕様上の制約を反映し、チュートリアルのコードを修正することで、Go言語の正しい使い方を提示しています。修正後のコード x := [3]int{1,2,3}; s := sum(x[:]) では、まず配列リテラル [3]int{1,2,3} の値が変数 x に代入されます。変数 x はメモリ上の固定された場所を持つため、アドレス可能です。したがって、x[:] のように x をスライスすることは有効な操作となります。

この修正は、Go言語のコンパイラがどのようにコードを解釈し、メモリを管理するかという、より深いレベルの理解に基づいています。

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

このコミットでは、主にGo言語の公式チュートリアル (doc/go_tutorial.html, doc/go_tutorial.tmpl) と、関連するサンプルプログラム (doc/progs/sum.go) の3つのファイルが変更されています。

変更の核心は、以下のコードパターンを修正することです。

変更前:

s := sum([3]int{1,2,3}[:])
s := sum([...]int{1,2,3}[:])

変更後:

x := [3]int{1,2,3}
s := sum(x[:])

x := [...]int{1,2,3}
s := sum(x[:])

具体的なファイルごとの変更は以下の通りです。

doc/go_tutorial.html および doc/go_tutorial.tmpl (HTMLテンプレート)

これらのファイルでは、Go言語のチュートリアル内のコード例が修正されています。

--- a/doc/go_tutorial.html
+++ b/doc/go_tutorial.html
@@ -343,19 +343,21 @@ Using slices one can write this function (from <code>sum.go</code>):
 Note how the return type (<code>int</code>) is defined for <code>sum</code> by stating it
 after the parameter list.\n <p>\n-To call the function, we slice the array.  This intricate call (we\'ll show\n+To call the function, we slice the array.  This code (we\'ll show\n a simpler way in a moment) constructs\n an array and slices it:\n <p>\n <pre>\n-s := sum([3]int{1,2,3}[:])\n+x := [3]int{1,2,3}\n+s := sum(x[:])\n </pre>\n <p>\n If you are creating a regular array but want the compiler to count the\n elements for you, use <code>...</code> as the array size:\n <p>\n <pre>\n-s := sum([...]int{1,2,3}[:])\n+x := [...]int{1,2,3}\n+s := sum(x[:])\n </pre>\n <p>\n That\'s fussier than necessary, though.

doc/go_tutorial.tmpl も同様の変更が加えられています。

doc/progs/sum.go (サンプルプログラム)

このファイルは、チュートリアルで参照される sum 関数のサンプル実装です。main 関数内の呼び出しが修正されています。

--- a/doc/progs/sum.go
+++ b/doc/progs/sum.go
@@ -15,6 +15,7 @@ func sum(a []int) int { // returns an int
 }
 
 func main() {
-\ts := sum([3]int{1, 2, 3}[:]) // a slice of the array is passed to sum
+\tx := [3]int{1, 2, 3}\n+\ts := sum(x[:]) // a slice of the array is passed to sum
 \tfmt.Print(s, \"\\n\")
 }

これらの変更により、Go言語のチュートリアルとサンプルコードが、配列リテラルのスライスに関するGo言語の正確な仕様に準拠するようになりました。

コアとなるコードの解説

変更されたコードは、Go言語における配列リテラルとスライスの正しい使用方法を示しています。

変更前のコード:

s := sum([3]int{1,2,3}[:])

このコードは、[3]int{1,2,3} という配列リテラルをその場で作成し、それを直接スライスしようとしています。Go言語の仕様では、スライス演算子 [:] のオペランドはアドレス可能である必要があります。しかし、[3]int{1,2,3} のような配列リテラルは、一時的な値であり、それ自体はアドレス可能ではありません。そのため、このコードはコンパイルエラーとなるか、少なくともGoの仕様に反する動作となります。

変更後のコード:

x := [3]int{1,2,3}
s := sum(x[:])

この修正されたコードは、Go言語の仕様に完全に準拠しています。

  1. x := [3]int{1,2,3}: まず、配列リテラル [3]int{1,2,3} の値が、新しく宣言された変数 x に代入されます。変数 x はメモリ上の固定された場所を持つため、アドレス可能です。
  2. s := sum(x[:]): 次に、アドレス可能な変数 x に対してスライス演算子 [:] が適用され、x の全体を参照するスライスが作成されます。このスライスが sum 関数に引数として渡されます。

同様に、配列の長さをコンパイラに推論させる ... を使用したケースも修正されています。

変更前のコード:

s := sum([...]int{1,2,3}[:])

変更後のコード:

x := [...]int{1,2,3}
s := sum(x[:])

この変更も、[3]int{1,2,3} の場合と同様に、配列リテラルを一度変数 x に代入することで、アドレス可能性の問題を解決しています。

この修正は、Go言語の基本的なデータ構造である配列とスライスの間の関係、およびアドレス可能性という重要な概念を、学習者に対してより正確に伝えるためのものです。これにより、Go言語のコードがより堅牢で、仕様に準拠したものになります。

関連リンク

  • Go Change-Id: I2111111111111111111111111111111111111111 (これはコミットメッセージに記載されている https://golang.org/cl/5437120 に対応するGoの内部的な変更IDです。通常、Goの変更はGerritというコードレビューシステムで管理されており、その変更セットに割り当てられるIDです。)
  • Go Playground: https://go.dev/play/ (Goのコードをオンラインで実行・テストできる環境)

参考にした情報源リンク