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

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

このコミットは、Go言語の標準ライブラリである net パッケージから、fmt および bytes パッケージへの直接的な依存関係を削除することを目的としています。これにより、net パッケージの独立性を高め、間接的な依存関係をさらに削減するための基盤を築いています。

コミット

commit 610b5b2fd8a31ac3855088a0ea2aece8d88d7521
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Mon Mar 5 13:36:05 2012 -0800

    net: remove all direct fmt and bytes imports
    
    Once dnsMsg stops using reflect, we lose even more
    indirect dependencies.
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/5751043

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

https://github.com/golang/go/commit/610b5b2fd8a31ac3855088a0ea2aece8d88d7521

元コミット内容

net: remove all direct fmt and bytes imports

dnsMsgreflect を使用しなくなると、さらに多くの間接的な依存関係が失われます。

変更の背景

Go言語の標準ライブラリは、各パッケージが可能な限り独立し、最小限の依存関係を持つように設計されています。これは、コンパイル時間の短縮、バイナリサイズの削減、そしてライブラリ全体の堅牢性と保守性の向上に寄与します。

fmt パッケージは、その柔軟なフォーマット機能のために内部的に reflect パッケージを使用することが多く、これが間接的な依存関係の連鎖を引き起こす可能性があります。同様に、bytes パッケージもバイトスライス操作のための便利な機能を提供しますが、net パッケージのような低レベルでパフォーマンスが重視される部分では、より直接的な操作が望ましい場合があります。

このコミットの背景には、net パッケージのコア部分からこれらの汎用的なパッケージへの直接的な依存を排除し、より自己完結型で効率的なコードベースを目指すという明確な意図があります。コミットメッセージにある「Once dnsMsg stops using reflect, we lose even more indirect dependencies.」という記述は、この変更がより広範な依存関係削減戦略の一部であることを示唆しています。dnsMsgreflect を使用しなくなることで、fmt パッケージが reflect に依存している場合の間接的な依存関係も解消されるため、このコミットは将来のさらなる最適化への布石とも言えます。

前提知識の解説

  • Go言語の標準ライブラリ設計思想: Go言語の標準ライブラリは、シンプルさ、効率性、そして依存関係の最小化を重視しています。特に、net のような基盤となるパッケージは、可能な限り外部依存を減らすことで、安定性とパフォーマンスを確保します。
  • fmt パッケージ: fmt パッケージは、Go言語におけるフォーマット済みI/O(入出力)を提供します。fmt.Sprintf のような関数は、様々な型の値を文字列に変換する際に非常に便利ですが、その汎用性のために内部でリフレクション(reflect パッケージ)を使用することがあります。リフレクションは強力な機能ですが、実行時のオーバーヘッドや、予期せぬ依存関係の導入につながる可能性があります。
  • bytes パッケージ: bytes パッケージは、バイトスライスを操作するためのユーティリティ関数を提供します。bytes.Buffer は可変長のバイトバッファを効率的に扱うための型であり、bytes.Equal は2つのバイトスライスが等しいかを比較します。
  • reflect パッケージ: reflect パッケージは、Goプログラムが実行時に自身の構造を検査・操作することを可能にします。fmt パッケージが任意の型の値をフォーマットする際に、その型の情報を動的に取得するために reflect を利用することがあります。
  • DNS逆引き (in-addr.arpa, ip6.arpa): IPアドレスからドメイン名を解決する「逆引き」に使用される特殊なDNSドメインです。IPv4アドレスは in-addr.arpa ドメインで、IPv6アドレスは ip6.arpa ドメインで逆順に表現されます。
  • syscall パッケージ: syscall パッケージは、オペレーティングシステムの低レベルなシステムコールへのアクセスを提供します。ネットワーク操作において、ソケットオプションの設定など、OS固有の機能を利用する際に使用されます。

技術的詳細

このコミットの主要な技術的変更は、fmt および bytes パッケージのインポートを削除し、その機能を手動で再実装するか、より低レベルな代替手段に置き換えることです。

  1. fmt.Sprintf の置き換え:
    • fmt.Sprintf は、文字列のフォーマットと数値から文字列への変換に広く使用されます。このコミットでは、net/dnsclient.go 内のIPアドレスの逆引き処理において、fmt.Sprintf を使用せずに、itoa (integer to ASCII) のようなヘルパー関数と文字列連結を組み合わせて数値から文字列への変換を行っています。これにより、fmt パッケージ全体をインポートする必要がなくなります。
    • src/pkg/net/interface_linux.go では、fmt.Sscanf を使用して16進数文字列をバイトスライスにパースしていた箇所が、xtoi2 (hexadecimal to integer) のようなヘルパー関数とループによる手動パースに置き換えられています。
  2. bytes.Buffer の置き換え:
    • bytes.Buffer は、効率的なバイトスライス構築に用いられます。net/dnsclient.go のIPv6逆引き処理では、bytes.Buffer の代わりに、make([]byte, ...) で初期容量を確保したバイトスライスを作成し、append 関数を使ってバイトを追加していく方式に変更されています。これにより、bytes パッケージへの依存がなくなります。
  3. bytes.Equal の置き換え:
    • bytes.Equal は2つのバイトスライスが等しいかを比較します。net/sockopt.go では、bytes.Equal の代わりに、bytesEqual というローカル(おそらく net パッケージ内で定義された未エクスポートの)ヘルパー関数が使用されています。これは、bytes.Equal と同等の機能を提供しますが、bytes パッケージのインポートを回避します。
  4. panic メッセージの簡素化:
    • net/tcpsock_posix.go では、panic(fmt.Sprintf("unexpected type in sockaddrToTCP: %T", sa)) のように、fmt.Sprintf を使って動的に型情報をパニックメッセージに含めていた箇所が、panic("unexpected type in sockaddrToTCP") と固定文字列に変更されています。これにより、fmt パッケージへの依存が解消されます。動的な型情報の表示はデバッグには役立ちますが、本番環境でのパニックメッセージとしては簡潔さが優先される場合や、依存関係削減の目的のためにトレードオフとして受け入れられています。

これらの変更は、net パッケージが外部の汎用パッケージに依存することなく、自身の内部で必要な機能を完結させるという設計原則を強化するものです。これにより、net パッケージのビルド時の依存関係が減少し、より軽量で独立したコンポーネントとなります。

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

src/pkg/net/dnsclient.go

--- a/src/pkg/net/dnsclient.go
+++ b/src/pkg/net/dnsclient.go
@@ -5,8 +5,6 @@
 package net
 
 import (
-	"bytes"
-	"fmt"
 	"math/rand"
 	"sort"
 )
@@ -45,20 +43,22 @@ func reverseaddr(addr string) (arpa string, err error) {
 		return "", &DNSError{Err: "unrecognized address", Name: addr}
 	}
 	if ip.To4() != nil {
-		return fmt.Sprintf("%d.%d.%d.%d.in-addr.arpa.", ip[15], ip[14], ip[13], ip[12]), nil
+		return itoa(int(ip[15])) + "." + itoa(int(ip[14])) + "." + itoa(int(ip[13])) + "." +
+			itoa(int(ip[12])) + ".in-addr.arpa.", nil
 	}
 	// Must be IPv6
-	var buf bytes.Buffer
+	buf := make([]byte, 0, len(ip)*4+len("ip6.arpa."))
 	// Add it, in reverse, to the buffer
 	for i := len(ip) - 1; i >= 0; i-- {
-		s := fmt.Sprintf("%02x", ip[i])
-		buf.WriteByte(s[1])
-		buf.WriteByte('.')
-		buf.WriteByte(s[0])
-		buf.WriteByte('.')
+		v := ip[i]
+		buf = append(buf, hexDigit[v&0xF])
+		buf = append(buf, '.')
+		buf = append(buf, hexDigit[v>>4])
+		buf = append(buf, '.')
 	}
 	// Append "ip6.arpa." and return (buf already has the final .)
-	return buf.String() + "ip6.arpa.", nil
+	buf = append(buf, "ip6.arpa."...)
+	return string(buf), nil
 }
 
 // Find answer for name in dns message.

src/pkg/net/interface_linux.go

--- a/src/pkg/net/interface_linux.go
+++ b/src/pkg/net/interface_linux.go
@@ -7,7 +7,6 @@
 package net
 
 import (
-	"fmt"
 	"os"
 	"syscall"
 	"unsafe"
@@ -194,7 +193,9 @@ func parseProcNetIGMP(path string, ifi *Interface) []Addr {
 			name = f[1]
 		case len(f[0]) == 8:
 			if ifi == nil || name == ifi.Name {
-				fmt.Sscanf(f[0], "%08x", &b)
+				for i := 0; i+1 < len(f[0]); i += 2 {
+					b[i/2], _ = xtoi2(f[0][i:i+2], 0)
+				}
 				ifma := IPAddr{IP: IPv4(b[3], b[2], b[1], b[0])}
 				ifmat = append(ifmat, ifma.toAddr())
 			}
@@ -218,10 +219,11 @@ func parseProcNetIGMP6(path string, ifi *Interface) []Addr {
 			continue
 		}
 		if ifi == nil || f[1] == ifi.Name {
-			fmt.Sscanf(f[2], "%32x", &b)
+			for i := 0; i+1 < len(f[2]); i += 2 {
+				b[i/2], _ = xtoi2(f[2][i:i+2], 0)
+			}
 			ifma := IPAddr{IP: IP{b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]}}
 			ifmat = append(ifmat, ifma.toAddr())
-
 		}
 	}
 	return ifmat

src/pkg/net/sockopt.go

--- a/src/pkg/net/sockopt.go
+++ b/src/pkg/net/sockopt.go
@@ -9,7 +9,6 @@
 package net
 
 import (
-	"bytes"
 	"os"
 	"syscall"
 	"time"
@@ -98,7 +97,7 @@ func setIPv4MreqToInterface(mreq *syscall.IPMreq, ifi *Interface) error {
 		}
 	}
 done:
-	if bytes.Equal(mreq.Multiaddr[:], IPv4zero.To4()) {
+	if bytesEqual(mreq.Multiaddr[:], IPv4zero.To4()) {
 		return errNoSuchMulticastInterface
 	}
 	return nil

src/pkg/net/tcpsock_posix.go

--- a/src/pkg/net/tcpsock_posix.go
+++ b/src/pkg/net/tcpsock_posix.go
@@ -9,7 +9,6 @@
 package net
 
 import (
-	"fmt"
 	"io"
 	"os"
 	"syscall"
@@ -30,7 +29,7 @@ func sockaddrToTCP(sa syscall.Sockaddr) Addr {
 	default:
 		if sa != nil {
 			// Diagnose when we will turn a non-nil sockaddr into a nil.
-			panic(fmt.Sprintf("unexpected type in sockaddrToTCP: %T", sa))
+			panic("unexpected type in sockaddrToTCP")
 		}
 	}
 	return nil

コアとなるコードの解説

  • src/pkg/net/dnsclient.go:
    • import セクションから bytesfmt が削除されました。
    • reverseaddr 関数内のIPv4アドレスの逆引き処理では、fmt.Sprintf を使った文字列フォーマットが、itoa 関数(おそらく net パッケージ内で定義された整数を文字列に変換するヘルパー関数)と文字列連結に置き換えられました。これにより、fmt パッケージへの依存がなくなります。
    • IPv6アドレスの逆引き処理では、bytes.Buffer を使用してバイトスライスを構築していた部分が、make([]byte, ...) で事前に容量を確保したスライスを作成し、append を使ってバイトを追加する方式に変更されました。また、fmt.Sprintf("%02x", ip[i]) で16進数文字列を生成していた箇所は、hexDigit 配列(おそらく 0 から F までの16進数文字を格納した配列)を直接参照してバイトを構築する、より低レベルな方法に置き換えられました。これにより、bytesfmt の両方への依存が解消されます。
  • src/pkg/net/interface_linux.go:
    • import セクションから fmt が削除されました。
    • parseProcNetIGMP および parseProcNetIGMP6 関数内で、/proc/net/igmp および /proc/net/igmp6 から読み取った16進数文字列をパースするために使用されていた fmt.Sscanf が削除されました。代わりに、ループと xtoi2 関数(おそらく net パッケージ内で定義された16進数文字列を整数に変換するヘルパー関数)を組み合わせて、手動でバイトスライスに変換するロジックが実装されました。これにより、fmt パッケージへの依存がなくなります。
  • src/pkg/net/sockopt.go:
    • import セクションから bytes が削除されました。
    • setIPv4MreqToInterface 関数内で、bytes.Equal を使用してバイトスライスを比較していた箇所が、bytesEqual 関数(おそらく net パッケージ内で定義された未エクスポートのヘルパー関数)に置き換えられました。これにより、bytes パッケージへの依存がなくなります。
  • src/pkg/net/tcpsock_posix.go:
    • import セクションから fmt が削除されました。
    • sockaddrToTCP 関数内で、panic メッセージを fmt.Sprintf で動的に生成していた箇所が、固定文字列 panic("unexpected type in sockaddrToTCP") に変更されました。これにより、fmt パッケージへの依存が解消され、パニックメッセージの生成が簡素化されました。

これらの変更は、net パッケージが外部の汎用パッケージに依存することなく、自身の内部で必要な機能を完結させるという設計原則を強化するものです。これにより、net パッケージのビルド時の依存関係が減少し、より軽量で独立したコンポーネントとなります。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (上記「関連リンク」に記載)
  • Go言語のソースコード (このコミットが属するリポジトリ)
  • Go言語のコミット履歴とGerritレビューシステム (コミットメッセージ内の https://golang.org/cl/5751043 など)
  • 一般的なプログラミングにおける依存関係管理と最適化の原則
  • IPアドレスの逆引きに関するDNSの知識 (in-addr.arpa, ip6.arpa)
  • Linuxの /proc ファイルシステムに関する知識 (特に /proc/net/igmp など)
  • Go言語におけるバイトスライス操作の慣用的な方法 (make, append)
  • Go言語における文字列と数値の変換方法 (strconv パッケージの存在と、手動実装の背景)
  • Go言語の panic とエラーハンドリングの原則
  • Go言語の標準ライブラリの設計原則に関する一般的な議論と記事