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

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

このコミットは、Go言語の公式FAQドキュメントである doc/go_faq.html ファイルに新しいエントリを追加するものです。具体的には、「When should I use a pointer to an interface?」(いつインターフェースへのポインタを使うべきか?)という項目が追加され、Go言語におけるインターフェースとポインタの扱いに関する一般的な誤解を解消し、正しい理解を促すための解説が加えられています。

コミット

  • コミットハッシュ: 09cd13c51dabd709e79329a9b8591fc4d15b6f3f
  • 作者: Rob Pike r@golang.org
  • 日付: Fri Mar 15 11:38:50 2013 -0700

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

https://github.com/golang/go/commit/09cd13c51dabd709e79329a9b8591fc4d15b6f3f

元コミット内容

doc/go_faq.html: add entry about pointer to interface

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/7546050

変更の背景

Go言語のインターフェースは、その柔軟性と強力さからGoプログラミングの重要な要素ですが、同時に初心者にとっては理解が難しい概念の一つでもあります。特に、ポインタとインターフェースの組み合わせは、多くの開発者が混乱しやすいポイントです。

このコミットは、Go言語の設計者の一人であるRob Pike氏によって行われました。これは、Goコミュニティ内で「インターフェースへのポインタ」に関する誤解や質問が頻繁に発生していたことへの対応と考えられます。GoのFAQにこの項目を追加することで、公式な形でこの問題に対する明確な指針を提供し、開発者がよりGoらしい(idiomatic Go)コードを書けるように支援することが目的です。

具体的には、具象型へのポインタがインターフェースを満たすことができる一方で、インターフェース型へのポインタが別のインターフェースを満たすことはほとんどない、という重要な区別を明確にすることが背景にあります。この区別は、Goの型システムとインターフェースの動作を理解する上で非常に重要です。

前提知識の解説

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

1. インターフェース (Interfaces)

Goのインターフェースは、メソッドのシグネチャの集合を定義する型です。Goのインターフェースは、JavaやC#のような明示的な implements キーワードを必要とせず、型がインターフェースで定義されたすべてのメソッドを実装していれば、そのインターフェースを満たすと見なされます(構造的型付け)。

例: io.Writer インターフェース

type Writer interface {
    Write(p []byte) (n int, err error)
}

このインターフェースは、Write メソッドを持つ任意の型によって満たされます。

2. ポインタ (Pointers)

Goのポインタは、変数のメモリアドレスを保持する変数です。ポインタを使用することで、関数に大きなデータ構造をコピーせずに渡したり、関数内で元の変数の値を変更したりすることができます。

  • T 型の変数 v がある場合、&vv のアドレス(*T 型のポインタ)です。
  • p*T 型のポインタである場合、*pp が指す値です。

3. メソッドセット (Method Sets)

Goでは、型がインターフェースを満たすかどうかは、その型の「メソッドセット」によって決定されます。メソッドセットは、その型が持つメソッドの集合です。

  • 値型 (T) のメソッドセットは、レシーバが T であるすべてのメソッドを含みます。
  • *ポインタ型 (T) のメソッドセットは、レシーバが T または *T であるすべてのメソッドを含みます。

この違いが、ポインタがインターフェースを満たす場合と、値がインターフェースを満たす場合の違いを生み出します。例えば、func (t T) M()func (t *T) M() の両方を持つ型 T がある場合、TM を持つインターフェースを満たしますが、*TM を持つインターフェースを満たします。しかし、func (t *T) M() しか持たない場合、TM を持つインターフェースを満たしませんが、*T は満たします。

4. インターフェース値の内部構造

Goのインターフェース値は、内部的に2つの要素から構成されます。

  • 型 (type): インターフェース値が保持している具象型の情報。
  • 値 (value): インターフェース値が保持している具象型の値。

例えば、var w io.Writer というインターフェース変数に *bytes.Buffer 型の値を代入すると、w の内部では型が *bytes.Buffer、値が bytes.Buffer のインスタンスへのポインタ、となります。

技術的詳細

このFAQエントリの核心は、「インターフェースへのポインタはほとんど使わない」という原則と、その理由、そして唯一の例外についてです。

インターフェースへのポインタがほとんど使われない理由

Goのインターフェースは、具象型がそのインターフェースを満たすことで機能します。インターフェース変数自体は、既に具象型の「型」と「値」を内部に持っています。したがって、インターフェース変数へのポインタ(*interface)は、そのインターフェース変数自体のアドレスを指すことになります。

一般的なGoのプログラミングでは、インターフェースを介して具象型のメソッドを呼び出すことが目的です。インターフェースへのポインタを介してインターフェースのメソッドを呼び出すことは、通常は意味がありません。なぜなら、インターフェースへのポインタは、そのポインタが指すインターフェース値が持つ具象型のメソッドセットを直接変更したり、そのインターフェース値が保持する具象型の値を直接操作したりするものではないからです。

FAQで述べられているように、インターフェースへのポインタは「遅延評価を伴うトリッキーな状況」でのみ発生する可能性があります。これは非常に特殊なケースであり、通常のアプリケーション開発では遭遇することは稀です。

fmt.Fprintf(&w, "hello, world\n") がコンパイルエラーになる理由

FAQの例では、var w io.Writer と宣言されたインターフェース変数に対して、fmt.Fprintf(&w, "hello, world\n") がコンパイルエラーになることが示されています。

fmt.Fprintf の最初の引数は io.Writer インターフェースを期待しています。 &w*io.Writer 型、つまり io.Writer インターフェースへのポインタです。

Goの型システムでは、*io.Writer 型は io.Writer インターフェースを満たしません。これは、io.Writer インターフェースが Write メソッドを要求するのに対し、*io.Writer 型のメソッドセットには Write メソッドが含まれていないためです。*io.Writerio.Writer のポインタであり、io.Writer 自体ではありません。

この点が、具象型へのポインタがインターフェースを満たす場合(例: *bytes.Bufferio.Writer を満たす)と混同されやすいポイントです。具象型 TWrite メソッドをポインタレシーバ (t *T) で実装している場合、*Tio.Writer を満たします。しかし、io.Writer インターフェース自体は、そのポインタ型 *io.WriterWrite メソッドを持つわけではありません。

interface{} への代入の例外

FAQでは、唯一の例外として「あらゆる値、インターフェースへのポインタでさえも、空のインターフェース型 (interface{}) の変数に代入できる」と説明されています。

interface{} は、メソッドを一切持たないインターフェースです。Goの型システムでは、すべての型は少なくとも0個のメソッドを持つため、すべての具象型は interface{} を満たします。これは、interface{} がGoにおける任意の型を表す汎用的な型として機能することを意味します。

したがって、&w (型 *io.Writer) は interface{} に代入できます。しかし、FAQが警告しているように、これは「ほとんど間違い」であり、結果として混乱を招く可能性があります。なぜなら、interface{} に代入された *io.Writer は、その内部で io.Writer インターフェース値へのポインタを保持しているだけであり、そのポインタを介して io.Writer のメソッドを直接呼び出すことはできないからです。型アサーションやリフレクションを使わない限り、その内部の io.Writer 値にアクセスすることは困難です。

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

このコミットでは、doc/go_faq.html ファイルに以下のHTMLスニペットが追加されています。

<h3 id="pointer_to_interface">
When should I use a pointer to an interface?</h3>

<p>
Almost never. Pointers to interface values arise only in rare, tricky situations involving
disguising an interface value's type for delayed evaluation.
</p>

<p>
It is however a common mistake to pass a pointer to an interface value
to a function expecting an interface. The compiler will complain about this
error but the situation can still be confusing, because sometimes a
<a href="#different_method_sets">pointer
is necessary to satisfy an interface</a>.
The insight is that although a pointer to a concrete type can satisfy
an interface, with one exception <em>a pointer to an interface can never satisfy a interface</em>.
</p>

<p>
Consider the variable declaration,
</p>

<pre>
var w io.Writer
</pre>

<p>
The printing function <code>fmt.Fprintf</code> takes as its first argument
a value that satisfies <code>io.Writer</code>—something that implements
the canonical <code>Write</code> method. Thus we can write
</p>

<pre>
fmt.Fprintf(w, "hello, world\n")
</pre>

<p>
If however we pass the address of <code>w</code>, the program will not compile.
</p>

<pre>
fmt.Fprintf(&amp;w, "hello, world\n") // Compile-time error.
</pre>

<p>
The one exception is that any value, even a pointer to an interface, can be assigned to
a variable of empty interface type (<code>interface{}</code>).
Even so, it's almost certainly a mistake if the value is a pointer to an interface;
the result can be confusing.
</p>

コアとなるコードの解説

追加されたHTMLコンテンツは、GoのFAQにおける新しいセクションを形成しています。

  • 見出し (<h3 id="pointer_to_interface">): 「When should I use a pointer to an interface?」という質問を提示し、このセクションの主題を明確にしています。
  • 最初の段落 (<p>Almost never...): インターフェースへのポインタは「ほとんど使わない」という結論を最初に提示し、その稀な使用例として「遅延評価のためにインターフェース値の型を偽装する」状況を挙げています。これは非常に高度で特殊なケースであり、通常のGoプログラミングでは意識する必要がないことを示唆しています。
  • 2番目の段落 (<p>It is however a common mistake...): インターフェースへのポインタを、インターフェースを期待する関数に渡すのが「よくある間違い」であることを指摘しています。コンパイラがエラーを出すにもかかわらず、開発者が混乱しやすいのは、具象型へのポインタがインターフェースを満たす場合があるためだと説明しています。そして、重要な洞察として「具象型へのポインタはインターフェースを満たすことができるが、インターフェースへのポインタはインターフェースを満たすことは決してない(一つの例外を除く)」と強調しています。この太字の部分が、このFAQエントリの最も重要なメッセージです。
  • コード例 (var w io.Writerfmt.Fprintf): io.Writer インターフェース変数 w を宣言し、fmt.Fprintf に直接 w を渡す正しい使い方を示しています。
  • エラーになるコード例 (fmt.Fprintf(&w, ...) ): fmt.Fprintfw のアドレス (&w) を渡すとコンパイルエラーになることを示し、その理由が *io.Writerio.Writer インターフェースを満たさないためであることを暗に示しています。
  • 例外の解説 (<p>The one exception is that any value...): 唯一の例外として、あらゆる値(インターフェースへのポインタも含む)が空のインターフェース型 (interface{}) に代入できることを説明しています。しかし、これも「ほとんど間違い」であり、混乱を招く可能性があると警告しています。

全体として、このFAQエントリは、Goのインターフェースとポインタの間の微妙な関係について、明確かつ実践的なガイダンスを提供することを目的としています。特に、具象型へのポインタとインターフェース型へのポインタの間の重要な違いを強調し、一般的な誤解を解消しようとしています。

関連リンク

参考にした情報源リンク

  • GitHubコミットページ: https://github.com/golang/go/commit/09cd13c51dabd709e79329a9b8591fc4d15b6f3f
  • Go Code Review Comments (CL 7546050): https://golang.org/cl/7546050 (コミットメッセージに記載されているChange Listへのリンク)
  • Go言語のインターフェースに関する一般的な議論やブログ記事 (例: "Go interfaces explained", "Pointers vs Values in Go") - これらの具体的なURLはコミット情報には含まれていませんが、Goコミュニティで広く議論されているトピックです。