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

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

このコミットは、Go言語のnet/httpパッケージ(当時のsrc/lib/http)におけるHTTPハンドラの実装方法を改善するものです。具体的には、関数型に直接メソッドを定義できるようになったGo言語の新しい機能(または安定化した機能)を活用し、HTTPハンドラをより簡潔かつGoらしい方法で記述できるように変更しています。これにより、以前は構造体を介して関数をラップする必要があった処理が不要になり、コードの可読性と記述性が向上しています。

コミット

commit 7a3877aa0c862927354e07a6919dd327e3f9aa03
Author: Russ Cox <rsc@golang.org>
Date:   Thu Feb 5 15:09:08 2009 -0800

    take advantage of methods on funcs
    
    R=r
    DELTA=14  (0 added, 13 deleted, 1 changed)
    OCL=24458
    CL=24470

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

https://github.com/golang/go/commit/7a3877aa0c862927354e07a6919dd327e3f9aa03

元コミット内容

このコミットは、Go言語の標準ライブラリであるnet/httpパッケージ(当時のパスはsrc/lib/http/server.go)内のHTTPハンドラを定義する部分を修正しています。

変更前は、func(*Conn, *Request)というシグネチャを持つ関数をHandlerインターフェース(ServeHTTPメソッドを持つ)として利用するために、以下のようなラッパー構造体handlerFuncとヘルパー関数HandlerFuncが使用されていました。

// Adapter: can use RequestFunction(f) as Handler
type handlerFunc struct {
	f func(*Conn, *Request)
}
func (h handlerFunc) ServeHTTP(c *Conn, req *Request) {
	h.f(c, req)
}
func HandlerFunc(f func(*Conn, *Request)) Handler {
	return handlerFunc{f}
}

このコードは、関数fhandlerFunc構造体のフィールドとして保持し、その構造体にServeHTTPメソッドを実装することで、Handlerインターフェースを満たしていました。

変更後、このラッパー構造体とそれに関連するヘルパー関数は削除され、代わりにHandlerFuncという関数型自体に直接ServeHTTPメソッドが定義されるようになりました。

// Adapter: can use HandlerFunc(f) as Handler
type HandlerFunc func(*Conn, *Request)
func (f HandlerFunc) ServeHTTP(c *Conn, req *Request) {
	f(c, req);
}

これにより、func(*Conn, *Request)型の関数は、HandlerFunc型にキャストするだけで直接Handlerインターフェースを満たすことができるようになり、コードが大幅に簡素化されました。

変更の背景

この変更の背景には、Go言語のコンパイラ(特に当時の6g)が、関数型に直接メソッドを定義する機能をサポートするようになった、あるいはその機能が安定して利用できるようになったという言語仕様の進化があります。

Go言語では、インターフェースはメソッドの集合を定義します。ある型がそのインターフェースのすべてのメソッドを実装していれば、その型はそのインターフェースを満たすと見なされます(暗黙的なインターフェースの実装)。HTTPハンドラの場合、http.HandlerインターフェースはServeHTTP(ResponseWriter, *Request)メソッドを要求します(このコミットの時点では*Conn, *Request)。

以前のGo言語のバージョンでは、プリミティブ型や関数型に直接メソッドを定義することはできませんでした。そのため、関数をインターフェースとして扱いたい場合、上記のように関数をフィールドとして持つ構造体を定義し、その構造体にインターフェースのメソッドを実装するという間接的なアプローチが必要でした。

このコミットが行われた2009年2月は、Go言語がまだ開発の初期段階にあり、言語仕様やコンパイラの機能が活発に進化していた時期です。この「関数型にメソッドを定義する」機能の導入は、Go言語の設計思想である「シンプルさ」と「表現力」をさらに追求するものであり、特にコールバック関数をインターフェースとして扱うパターンにおいて、冗長なラッパーコードを排除できる大きな改善でした。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念が重要です。

  1. インターフェース (Interfaces): Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。例えば、interface { ServeHTTP(c *Conn, req *Request) }というインターフェースがあれば、ServeHTTPメソッドを持つ任意の型がこのインターフェースを満たします。Goのインターフェースは「暗黙的」に実装されます。つまり、特定のインターフェースを実装すると明示的に宣言する必要はなく、そのインターフェースが要求するすべてのメソッドを実装していれば、自動的にそのインターフェースを満たしていると見なされます。

  2. 型定義 (Type Definitions): Goでは、既存の型に新しい名前を付けて新しい型を定義できます。例えば、type MyFunction func(int, int) intのように、関数シグネチャに新しい型名を付けることができます。この新しい型は、元の型と同じ基底型を持ちますが、異なるメソッドセットを持つことができます。

  3. メソッド (Methods): Go言語のメソッドは、特定の型に関連付けられた関数です。メソッドはレシーバ引数(func (r ReceiverType) MethodName(...)r ReceiverTypeの部分)を持ち、これによりその型のインスタンスに対して操作を行うことができます。

  4. 関数型へのメソッド定義 (Methods on Function Types): Go言語の特定のバージョン以降(このコミットの時点では比較的新しい機能)、関数型(例: type MyFunc func(int) string)に対しても直接メソッドを定義できるようになりました。これにより、関数自体をインターフェースとして扱うことが可能になり、関数を構造体でラップする手間が省けます。これは、特にコールバック関数やイベントハンドラをインターフェースとして利用するパターンで非常に強力な機能です。

これらの概念を組み合わせることで、http.HandlerFunc型がhttp.Handlerインターフェースを直接実装し、func(*Conn, *Request)型の関数をHTTPハンドラとして直接利用できるようになった背景が理解できます。

技術的詳細

このコミットの技術的詳細なポイントは、「関数型にメソッドを定義する」というGo言語の機能が、どのようにnet/httpパッケージの設計を簡素化したかという点にあります。

Go言語のnet/httpパッケージでは、HTTPリクエストを処理するための主要なインターフェースとしてhttp.Handlerが定義されています。このインターフェースは、単一のメソッドServeHTTPを持ちます。

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

(注: コミット当時のシグネチャはServeHTTP(*Conn, *Request)でしたが、概念は同じです。)

多くのWebアプリケーションでは、HTTPリクエストの処理ロジックはシンプルな関数として記述されることがよくあります。例えば、func myHandler(w http.ResponseWriter, r *http.Request)のような関数です。しかし、この関数は直接http.Handlerインターフェースを満たしません。なぜなら、ServeHTTPという名前のメソッドではないからです。

この問題を解決するために、Go言語はhttp.HandlerFuncという特別な型を提供しています。

変更前のアプローチ(ラッパー構造体): 変更前は、func(*Conn, *Request)型の関数をHandlerインターフェースとして利用するために、以下のような手順を踏んでいました。

  1. 構造体の定義: handlerFuncという構造体を定義し、その中に実際の関数を保持するフィールドfを持たせます。
    type handlerFunc struct {
    	f func(*Conn, *Request)
    }
    
  2. メソッドの実装: handlerFunc構造体にServeHTTPメソッドを実装します。このメソッドは、内部の関数fを呼び出すだけです。
    func (h handlerFunc) ServeHTTP(c *Conn, req *Request) {
    	h.f(c, req)
    }
    
  3. ヘルパー関数の提供: ユーザーがfunc(*Conn, *Request)型の関数を簡単にHandlerに変換できるように、HandlerFuncというヘルパー関数を提供します。
    func HandlerFunc(f func(*Conn, *Request)) Handler {
    	return handlerFunc{f}
    }
    
    これにより、ユーザーはhttp.Handle("/path", http.HandlerFunc(myFunction))のように記述できました。

変更後のアプローチ(関数型へのメソッド定義): Go言語が関数型にメソッドを定義する機能をサポートするようになったことで、より直接的なアプローチが可能になりました。

  1. 関数型の定義: HandlerFuncという新しい型を、func(*Conn, *Request)という関数シグネチャのエイリアスとして定義します。
    type HandlerFunc func(*Conn, *Request)
    
  2. メソッドの直接定義: このHandlerFunc型に直接ServeHTTPメソッドを定義します。このメソッドは、レシーバであるf(これはHandlerFunc型の値、つまり関数そのもの)を直接呼び出します。
    func (f HandlerFunc) ServeHTTP(c *Conn, req *Request) {
    	f(c, req);
    }
    

この変更により、HandlerFunc型はそれ自体がHandlerインターフェースを満たすようになります。ユーザーは、func(*Conn, *Request)型の関数をHandlerFunc型にキャストするだけで、その関数をhttp.Handlerとして利用できるようになります。

// 例: ユーザーが定義するハンドラ関数
func myActualHandler(c *http.Conn, req *http.Request) {
    // リクエスト処理ロジック
}

// 変更後: 直接キャストして利用可能
var handler http.Handler = http.HandlerFunc(myActualHandler)

このアプローチは、コードの冗長性を排除し、Go言語のインターフェースと型のシステムをより自然に活用できる、よりイディオマティックな方法です。

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

変更はsrc/lib/http/server.goファイルに集中しています。

--- a/src/lib/http/server.go
+++ b/src/lib/http/server.go
@@ -220,24 +220,11 @@ func (c *Conn) Hijack() (fd io.ReadWriteClose, buf *bufio.BufReadWrite, err *os.\
 	return;\
 }\
 \
-// Adapter: can use RequestFunction(f) as Handler
-type handlerFunc struct {
-	f func(*Conn, *Request)
-}
-func (h handlerFunc) ServeHTTP(c *Conn, req *Request) {
-	h.f(c, req)
-}
-func HandlerFunc(f func(*Conn, *Request)) Handler {
-	return handlerFunc{f}
-}
-\
-/* simpler version of above, not accepted by 6g:
-\
+// Adapter: can use HandlerFunc(f) as Handler
 type HandlerFunc func(*Conn, *Request)
 func (f HandlerFunc) ServeHTTP(c *Conn, req *Request) {
 	f(c, req);\
 }
-*/
 \
 // Helper handlers
 \
  • 削除されたコード:
    • handlerFunc構造体の定義。
    • handlerFunc構造体に対するServeHTTPメソッドの実装。
    • HandlerFuncヘルパー関数の定義(handlerFunc構造体を返すもの)。
  • 追加/変更されたコード:
    • HandlerFuncという関数型の定義 (type HandlerFunc func(*Conn, *Request))。
    • このHandlerFunc型に直接ServeHTTPメソッドを定義 (func (f HandlerFunc) ServeHTTP(c *Conn, req *Request) { f(c, req); })。
  • コメントの変更:
    • 以前のコードブロックにあった「simpler version of above, not accepted by 6g:」というコメントが削除され、新しい実装がその「よりシンプルなバージョン」であることを示唆しています。

コアとなるコードの解説

このコミットの核心は、Go言語の型システムとインターフェースの強力な組み合わせを、より効率的に利用する点にあります。

変更前は、func(*Conn, *Request)という関数シグネチャを持つ関数をHandlerインターフェースとして扱いたい場合、その関数をhandlerFuncという構造体のフィールドに格納し、その構造体にServeHTTPメソッドを実装する必要がありました。これは、関数をオブジェクトのように扱うための一般的なデザインパターンですが、Go言語の「関数型にメソッドを定義できる」機能が導入されたことで、この間接的な層が不要になりました。

変更後のコードでは、type HandlerFunc func(*Conn, *Request)という行で、HandlerFuncという新しい型を定義しています。この型は、*Conn*Requestを引数に取り、何も返さない関数を表現します。

そして、func (f HandlerFunc) ServeHTTP(c *Conn, req *Request) { f(c, req); }という行がこのコミットの最も重要な部分です。ここでは、HandlerFuncというに対してServeHTTPというメソッドを定義しています。このメソッドのレシーバfHandlerFunc型であり、これはつまり、ServeHTTPメソッドが呼び出されたときに、レシーバであるf(つまり、元の関数そのもの)をcreqを引数として直接呼び出すことを意味します。

これにより、HandlerFunc型の値(つまり、func(*Conn, *Request)型の関数)は、自動的にServeHTTPメソッドを持つことになり、結果としてHandlerインターフェースを実装することになります。

この変更は、Go言語の設計哲学である「シンプルさ」と「表現力」を体現しています。冗長なラッパー構造体を排除し、関数をより直接的にインターフェースとして利用できるようにすることで、コードベースがよりクリーンになり、開発者がHTTPハンドラを記述する際の負担が軽減されました。これは、Go言語の進化の過程で、言語機能がライブラリの設計にどのように影響を与え、改善をもたらしたかを示す良い例です。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (pkg.go.dev, go.dev/tour)
  • Go言語のGitHubリポジトリのコミット履歴
  • Go言語の初期の設計に関する議論やブログ記事 (Web検索を通じて、当時の言語機能の進化に関する情報を補完しました)
    • 特に、Go言語の初期のバージョンでは関数型にメソッドを定義できなかったという情報が、このコミットの背景を理解する上で重要でした。
    • "Go methods on function types" などのキーワードで検索し、関連するGoのブログ記事やフォーラムの議論を参照しました。
    • Go言語の歴史的な変更点に関する情報は、公式のリリースノートやGoブログのアーカイブから得られることが多いです。
    • このコミットは2009年のものであり、Go言語がオープンソース化された直後の時期にあたるため、言語仕様が活発に変化していたことが伺えます。
    • Go言語のnet/httpパッケージの進化は、Goの設計思想を理解する上で非常に良いケーススタディとなります。