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

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

このコミットは、Go言語の実験的なSSHパッケージ (exp/ssh) における Server Listener の実装から、不要になった転送メソッドを削除するものです。具体的には、Go言語の構造体埋め込み(embedding)の特性を活かし、net.Listener インターフェースのメソッドを Listener 構造体が直接利用できるようにすることで、冗長なメソッド定義を排除し、コードの簡潔性と保守性を向上させています。

コミット

exp/ssh: remove unused forwarding methods in Server Listener

このコミットは、src/pkg/exp/ssh/server.go ファイル内の Listener 構造体から、net.Listener インターフェースのメソッドをラップしていた Addr() および Close() メソッドを削除し、Accept() メソッドの呼び出し方を修正しています。これは、Goの構造体埋め込み機能により、これらのメソッドが自動的に Listener 構造体で利用可能になるため、明示的な転送メソッドが不要になったためです。

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

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

元コミット内容

exp/ssh: remove unused forwarding methods in Server Listener

R=agl, rsc
CC=golang-dev
https://golang.org/cl/5436056

変更の背景

この変更の背景には、Go言語の構造体埋め込み(embedding)という強力な機能があります。Goの初期のバージョンでは、構造体内に他の構造体のインスタンスをフィールドとして持ち、そのフィールドのメソッドを外部に公開する場合、明示的に転送メソッド(forwarding methods)を定義する必要がありました。例えば、Listener 構造体が net.Listener 型のフィールド listener を持っていた場合、listener.Accept()listener.Addr()listener.Close() といったメソッドを Listener 構造体自身が提供するためには、それぞれ l.listener.Accept()l.listener.Addr()l.listener.Close() を呼び出すメソッドを Listener 構造体に定義する必要がありました。

しかし、Go言語の進化に伴い、構造体埋め込みのセマンティクスが強化されました。これにより、フィールド名を指定せずに型のみを構造体に埋め込むことで、埋め込まれた型のメソッドが外側の構造体のメソッドとして自動的に「昇格(promoted)」されるようになりました。この機能により、冗長な転送メソッドの定義が不要になり、コードがより簡潔になります。

このコミットは、まさにこの構造体埋め込みの利点を活用し、Listener 構造体から冗長な Addr()Close() メソッドを削除し、Accept() メソッドの呼び出しも埋め込みによって提供されるメソッドに切り替えることで、コードのクリーンアップと効率化を図っています。

前提知識の解説

Go言語の exp パッケージ

Go言語の標準ライブラリには、exp というプレフィックスを持つパッケージが存在することがあります。これは "experimental"(実験的)の略であり、まだ安定版として提供するには時期尚早であるか、APIが変更される可能性のある機能が含まれていることを示します。これらのパッケージは、将来的に標準ライブラリの一部となる可能性もありますが、その保証はありません。このコミットで変更されている exp/ssh パッケージもその一つで、SSHプロトコルをGoで扱うための実験的な実装を提供していました。

Go言語の net.Listener インターフェース

net.Listener はGo言語の標準ライブラリ net パッケージで定義されているインターフェースです。これは、ネットワーク接続をリッスン(待ち受け)するための一般的なインターフェースであり、以下の3つの主要なメソッドを定義しています。

  • Accept() (net.Conn, error): 新しいネットワーク接続が確立されるまでブロックし、確立された接続を表す net.Conn インターフェースとエラーを返します。
  • Close() error: リスナーを閉じ、それ以上の新しい接続を受け付けないようにします。
  • Addr() net.Addr: リスナーのネットワークアドレスを返します。

このインターフェースは、TCP、UDP、Unixドメインソケットなど、様々な種類のネットワークリスナーを抽象化するために使用されます。

Go言語の構造体埋め込み(Embedding)

Go言語には、他のプログラミング言語における継承に似た概念として「構造体埋め込み(embedding)」があります。しかし、Goの埋め込みは継承とは異なり、より正確には「コンポジション(composition)」の一種と見なされます。

構造体埋め込みは、ある構造体の中に、フィールド名を指定せずに別の構造体の型を記述することで実現されます。これにより、埋め込まれた構造体のフィールドやメソッドが、外側の構造体のフィールドやメソッドであるかのように直接アクセスできるようになります。

例:

type Base struct {
    Value int
}

func (b Base) GetValue() int {
    return b.Value
}

type Derived struct {
    Base // Base構造体を埋め込み
    Name string
}

func main() {
    d := Derived{
        Base: Base{Value: 10},
        Name: "Test",
    }
    fmt.Println(d.GetValue()) // BaseのGetValueメソッドがDerivedから直接呼び出せる
    fmt.Println(d.Value)     // BaseのValueフィールドがDerivedから直接アクセスできる
}

この機能の重要な点は、埋め込まれた型のメソッドが、外側の構造体のメソッドとして「昇格(promoted)」されることです。つまり、外側の構造体は、埋め込まれた型のインターフェースを自動的に満たすことができます。このコミットでは、Listener 構造体に net.Listener を埋め込むことで、net.Listener インターフェースが持つ Accept(), Addr(), Close() メソッドが Listener 構造体から直接呼び出せるようになり、明示的な転送メソッドが不要になったのです。

技術的詳細

このコミットの技術的な核心は、Go言語の構造体埋め込みによるインターフェースの自動的な満たし方とメソッドの昇格にあります。

変更前、Listener 構造体は net.Listener 型のフィールド listener を持っていました。

type Listener struct {
	listener net.Listener // 明示的なフィールド名
	config   *ServerConfig
}

この場合、Listener 構造体が net.Listener インターフェースのメソッド(Accept, Addr, Close)を提供するためには、以下のように明示的な転送メソッドを定義する必要がありました。

func (l *Listener) Accept() (*ServerConn, error) {
	c, err := l.listener.Accept() // l.listener を介して呼び出し
	// ...
}

func (l *Listener) Addr() net.Addr {
	return l.listener.Addr() // l.listener を介して呼び出し
}

func (l *Listener) Close() error {
	return l.listener.Close() // l.listener を介して呼び出し
}

変更後、Listener 構造体は net.Listener 型をフィールド名を付けずに埋め込みました。

type Listener struct {
	net.Listener // フィールド名を付けずに型を埋め込み
	config *ServerConfig
}

この変更により、Goコンパイラは Listener 構造体が net.Listener インターフェースのすべてのメソッド(Accept, Addr, Close)を自動的に「昇格」させたと解釈します。つまり、Listener 型のインスタンスに対して l.Accept()l.Addr()l.Close() を直接呼び出すことが可能になります。これらの呼び出しは、内部的に埋め込まれた net.Listener インスタンスの対応するメソッドに転送されます。

結果として、Addr()Close() メソッドは Listener 構造体から完全に削除されました。なぜなら、それらは埋め込みによって自動的に提供されるようになったため、手動で定義する必要がなくなったからです。また、Accept() メソッドの内部実装も l.listener.Accept() から l.Listener.Accept() に変更されました。これは、埋め込まれたフィールドにアクセスする際に、フィールド名を省略して直接アクセスできるようになったためです。

この変更は、コードの冗長性を減らし、Listener 構造体が net.Listener インターフェースをよりGoらしい慣用的な方法で「実装」していることを明確に示しています。

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

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

--- a/src/pkg/exp/ssh/server.go
+++ b/src/pkg/exp/ssh/server.go
@@ -636,15 +636,15 @@ func (s *ServerConn) Accept() (Channel, error) {

 // A Listener implements a network listener (net.Listener) for SSH connections.
 type Listener struct {
-	listener net.Listener
-	config   *ServerConfig
+	net.Listener
+	config *ServerConfig
 }

 // Accept waits for and returns the next incoming SSH connection.
 // The receiver should call Handshake() in another goroutine
 // to avoid blocking the accepter.
 func (l *Listener) Accept() (*ServerConn, error) {
-	c, err := l.listener.Accept()
+	c, err := l.Listener.Accept()
 	if err != nil {
 		return nil, err
 	}
@@ -652,16 +652,6 @@ func (l *Listener) Accept() (*ServerConn, error) {
 	return conn, nil
 }

-// Addr returns the listener's network address.
-func (l *Listener) Addr() net.Addr {
-	return l.listener.Addr()
-}
-
-// Close closes the listener.
-func (l *Listener) Close() error {
-	return l.listener.Close()
-}
-
 // Listen creates an SSH listener accepting connections on
 // the given network address using net.Listen.
 func Listen(network, addr string, config *ServerConfig) (*Listener, error) {

具体的な変更点は以下の通りです。

  1. Listener 構造体の定義変更:
    • - listener net.Listener が削除され、+ net.Listener が追加されました。これにより、net.Listener 型がフィールド名を付けずに Listener 構造体に埋め込まれました。
  2. Accept() メソッドの実装変更:
    • - c, err := l.listener.Accept() が削除され、+ c, err := l.Listener.Accept() が追加されました。これは、埋め込みによって net.ListenerAccept メソッドが Listener 構造体から直接アクセス可能になったため、明示的なフィールド名 listener を介する必要がなくなったことを示します。
  3. Addr() メソッドの削除:
    • func (l *Listener) Addr() net.Addr { return l.listener.Addr() } の定義全体が削除されました。
  4. Close() メソッドの削除:
    • func (l *Listener) Close() error { return l.listener.Close() } の定義全体が削除されました。

コアとなるコードの解説

このコミットのコアとなるコードの変更は、Go言語の構造体埋め込みの原則を直接反映しています。

  • type Listener struct { net.Listener; config *ServerConfig }: この行が最も重要な変更です。以前は listener net.Listener という名前付きフィールドとして net.Listener を保持していましたが、この変更により net.Listener が匿名フィールドとして Listener 構造体に埋め込まれました。Goの仕様では、匿名フィールドとして埋め込まれた構造体やインターフェースのメソッドは、外側の構造体のメソッドとして「昇格」されます。これにより、Listener 型のインスタンスは、あたかも自身が Accept(), Addr(), Close() メソッドを持っているかのように振る舞うことができます。

  • func (l *Listener) Accept() (*ServerConn, error) { c, err := l.Listener.Accept() ... }: Accept() メソッドの内部で、以前は l.listener.Accept() と明示的に埋め込まれたフィールド名を使っていましたが、埋め込みによって net.Listener のメソッドが昇格されたため、l.Listener.Accept() と直接呼び出すことができるようになりました。ここで l.Listener は、埋め込まれた net.Listener 型の匿名フィールドを指します。これは、Goが匿名フィールドに対して、その型名と同じ名前でアクセスできるという糖衣構文(syntactic sugar)を提供しているためです。

  • Addr() および Close() メソッドの削除: これらのメソッドは、以前は l.listener.Addr()l.listener.Close() を単に呼び出すだけの転送メソッドでした。構造体埋め込みにより、net.ListenerAddr()Close() メソッドが Listener 構造体に自動的に昇格されたため、これらの冗長な転送メソッドは完全に不要となり、削除されました。これにより、コードの行数が減り、より簡潔で読みやすくなりました。

この変更は、Go言語の設計思想である「コンポジションによる再利用」と「簡潔さ」を体現しています。不要なボイラープレートコードを削減し、Listener 構造体が net.Listener インターフェースの振る舞いを自然に継承していることを明確に示しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語の構造体埋め込みに関する一般的な解説記事
  • net パッケージのGoDoc
  • コミットメッセージと差分情報