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

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

このコミットは、Go言語のosパッケージ、特にfile_plan9.goファイルにおける変更を扱っています。具体的には、文字列操作を行うstringsパッケージへの依存を排除するために、HasPrefixLastIndex関数をosパッケージ内部で再実装しています。

コミット

commit 4237ffe5ead50a305c52630fd6726ddf220cefa0
Author: David du Colombier <0intro@gmail.com>
Date:   Sat Dec 21 01:22:10 2013 +0100

    os: reimplement HasPrefix and LastIndex to not depend on strings
    
    R=golang-codereviews, rsc
    CC=golang-codereviews, jas
    https://golang.org/cl/44790043

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

https://github.com/golang/go/commit/4237ffe5ead50a305c52630fd6726ddf220cefa0

元コミット内容

このコミットの元の内容は、osパッケージがstringsパッケージのHasPrefixおよびLastIndex関数に依存している点を解消することです。これにより、osパッケージの独立性を高め、特定の環境(特にPlan 9)でのビルドや実行時の依存関係を簡素化することが目的とされています。

変更の背景

Go言語の標準ライブラリは、各パッケージが可能な限り独立して機能するように設計されています。しかし、一部のパッケージでは、他のパッケージのユーティリティ関数に依存している場合があります。このコミットの背景には、osパッケージがstringsパッケージに依存しているという状況がありました。

特に、file_plan9.goというファイル名が示すように、この変更はPlan 9オペレーティングシステム向けのGoランタイムの挙動に関連しています。Plan 9は、Go言語の開発者であるRob Pike、Ken Thompson、Robert GriesemerらがBell Labsで開発した分散オペレーティングシステムであり、Go言語の設計思想に大きな影響を与えています。Go言語の初期の段階では、Plan 9環境での動作が非常に重視されており、そのための特定の最適化や依存関係の管理が行われていました。

osパッケージのような低レベルのシステムコールを扱うパッケージが、より高レベルの文字列操作パッケージに依存していることは、いくつかの問題を引き起こす可能性があります。

  1. 循環依存の可能性: 間接的にではあるが、stringsパッケージがosパッケージに依存するような状況が発生した場合、循環依存が発生し、ビルドシステムやパッケージ管理が複雑になる可能性があります。
  2. ビルドサイズの増加: 不要な依存関係は、最終的なバイナリのサイズを増加させる可能性があります。特に組み込みシステムやリソースが限られた環境では、これは重要な考慮事項となります。
  3. クロスコンパイルの複雑化: 特定の環境(この場合はPlan 9)向けにビルドする際に、依存関係が少ない方がビルドプロセスが簡素化されます。
  4. パフォーマンスの最適化: stringsパッケージの汎用的な関数を使用するよりも、osパッケージの特定のユースケースに特化した実装を行うことで、わずかながらもパフォーマンスの向上が期待できる場合があります。

このコミットは、これらの問題を解決し、osパッケージの独立性と効率性を高めることを目的としています。

前提知識の解説

このコミットを理解するためには、以下の前提知識が役立ちます。

  • Go言語のパッケージシステム: Go言語は、コードをパッケージという単位で管理します。パッケージは、関連する機能や型、関数をまとめたもので、他のパッケージからインポートして利用できます。import文は、あるパッケージが別のパッケージの機能を利用するために必要です。
  • osパッケージ: Go言語の標準ライブラリの一部で、オペレーティングシステムとのインタラクション(ファイル操作、プロセス管理、環境変数など)を提供します。低レベルのシステムコールを抽象化し、クロスプラットフォームなインターフェースを提供します。
  • stringsパッケージ: Go言語の標準ライブラリの一部で、文字列操作のためのユーティリティ関数(文字列の検索、置換、分割、結合など)を提供します。
  • HasPrefix関数: stringsパッケージに含まれる関数で、ある文字列が特定のプレフィックス(接頭辞)で始まるかどうかを判定します。 例: strings.HasPrefix("foobar", "foo")true を返します。
  • LastIndex関数: stringsパッケージに含まれる関数で、ある文字列内で特定のサブストリングまたは文字が最後に現れるインデックスを返します。 例: strings.LastIndex("foo/bar/baz", "/")7 を返します(bazの前の/のインデックス)。
  • Plan 9: ベル研究所で開発された分散オペレーティングシステム。Go言語の設計思想に影響を与えたことで知られています。Go言語の初期のランタイムには、Plan 9に特化したコードが含まれていました。file_plan9.goというファイルは、Plan 9環境でのファイルシステム操作に関するコードを記述するためのものです。
  • 依存関係の管理: ソフトウェア開発において、あるモジュールやライブラリが他のモジュールやライブラリに依存している状態を指します。依存関係が少ないほど、モジュールの独立性が高まり、テスト、デプロイ、メンテナンスが容易になります。

技術的詳細

このコミットの技術的な核心は、osパッケージがstringsパッケージに依存していたHasPrefixLastIndexの機能を、osパッケージ内で独自に実装し直した点にあります。

元のコードでは、rename関数内でパスの操作を行う際に、strings.LastIndexstrings.HasPrefixを使用していました。

// 変更前
dirname := oldname[:strings.LastIndex(oldname, "/")+1]
if strings.HasPrefix(newname, dirname) {
    newname = newname[len(dirname):]
}

この依存関係を解消するため、コミットでは以下の2つの新しいヘルパー関数をfile_plan9.go内に定義しています。

  1. hasPrefix(s, prefix string) bool: この関数は、strings.HasPrefixと同様の機能を提供します。文字列sprefixで始まるかどうかを判定します。実装は非常にシンプルで、sの長さがprefixの長さ以上であることと、sの先頭からprefixの長さ分の部分文字列がprefixと完全に一致するかどうかを比較します。

    func hasPrefix(s, prefix string) bool {
        return len(s) >= len(prefix) && s[0:len(prefix)] == prefix
    }
    
  2. lastIndex(s string, sep byte) int: この関数は、strings.LastIndexの特定のバリアントとして実装されています。元のコードではstrings.LastIndex(oldname, "/")のように、単一のバイト(/)を検索していました。このため、lastIndex関数は、文字列s内で特定のバイトsepが最後に現れるインデックスを返します。実装は、文字列の末尾から先頭に向かってループし、sepと一致するバイトが見つかったらそのインデックスを返します。見つからなかった場合は-1を返します。

    func lastIndex(s string, sep byte) int {
        for i := len(s) - 1; i >= 0; i-- {
            if s[i] == sep {
                return i
            }
        }
        return -1
    }
    

これらの内部関数を定義することで、osパッケージはstringsパッケージをインポートする必要がなくなり、依存関係が解消されました。これは、Go言語の標準ライブラリにおけるモジュール性、独立性、そして特定の環境(この場合はPlan 9)での効率性を追求する設計哲学の一例と言えます。

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

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

--- a/src/pkg/os/file_plan9.go
+++ b/src/pkg/os/file_plan9.go
@@ -6,7 +6,6 @@ package os
 
 import (
 	"runtime"
-	"strings"
 	"syscall"
 	"time"
 )
@@ -314,9 +313,24 @@ func Remove(name string) error {
 	return nil
 }
 
+// HasPrefix from the strings package.
+func hasPrefix(s, prefix string) bool {
+	return len(s) >= len(prefix) && s[0:len(prefix)] == prefix
+}
+
+// Variant of LastIndex from the strings package.
+func lastIndex(s string, sep byte) int {
+	for i := len(s) - 1; i >= 0; i-- {
+		if s[i] == sep {
+			return i
+		}
+	}
+	return -1
+}
+
 func rename(oldname, newname string) error {
-	dirname := oldname[:strings.LastIndex(oldname, "/")+1]
-	if strings.HasPrefix(newname, dirname) {
+	dirname := oldname[:lastIndex(oldname, '/')+1]
+	if hasPrefix(newname, dirname) {
 		newname = newname[len(dirname):]
 	}
 

コアとなるコードの解説

このコミットのコアとなる変更は以下の3点です。

  1. stringsパッケージのインポート削除: src/pkg/os/file_plan9.goファイルのimportブロックから"strings"が削除されました。これは、このファイルがもはやstringsパッケージの機能に依存しないことを意味します。

    -	"strings"
    
  2. hasPrefix関数の追加: strings.HasPrefixの機能を代替するhasPrefix関数が追加されました。この関数は、Goの基本的な文字列スライスと長さの比較を用いて、プレフィックスの一致を効率的にチェックします。

    // HasPrefix from the strings package.
    func hasPrefix(s, prefix string) bool {
    	return len(s) >= len(prefix) && s[0:len(prefix)] == prefix
    }
    

    この実装は、strings.HasPrefixの内部実装と非常に似ており、Go言語における文字列の効率的な操作方法を示しています。

  3. lastIndex関数の追加: strings.LastIndexの特定のユースケース(単一バイトの検索)を代替するlastIndex関数が追加されました。この関数は、文字列を逆順に走査し、指定されたバイトが見つかった最初の位置(つまり最後の出現位置)を返します。

    // Variant of LastIndex from the strings package.
    func lastIndex(s string, sep byte) int {
    	for i := len(s) - 1; i >= 0; i-- {
    		if s[i] == sep {
    			return i
    		}
    	}
    	return -1
    }
    

    この実装は、strings.LastIndexByte(Go 1.5で追加された)の機能に相当し、特定の文字(バイト)の最後の出現位置を効率的に見つけるための一般的なパターンです。

  4. rename関数内の呼び出し箇所の変更: rename関数内で、以前strings.LastIndexstrings.HasPrefixが呼び出されていた箇所が、新しく定義されたlastIndexhasPrefixに置き換えられました。

    -	dirname := oldname[:strings.LastIndex(oldname, "/")+1]
    -	if strings.HasPrefix(newname, dirname) {
    +	dirname := oldname[:lastIndex(oldname, '/')+1]
    +	if hasPrefix(newname, dirname) {
    

    これにより、rename関数はstringsパッケージへの依存を完全に解消し、osパッケージ内で完結するようになりました。

これらの変更は、Go言語の標準ライブラリが、可能な限り自己完結的であり、不必要な依存関係を排除しようとする設計原則を反映しています。特に、低レベルのシステムパッケージにおいては、依存関係を最小限に抑えることで、堅牢性、移植性、そしてパフォーマンスを向上させることができます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコードリポジトリ
  • Go言語のコードレビューシステム (Gerrit) 上の関連する変更リスト (CL): https://golang.org/cl/44790043
  • Go言語の設計に関する議論やブログ記事 (特にPlan 9との関連性について)
  • strings.HasPrefixstrings.LastIndexのGo言語のソースコード実装
  • Go言語のパッケージ設計に関する一般的なベストプラクティス