[インデックス 12287] ファイルの概要
コミット
- コミットハッシュ:
fc268acf05adf5d0437ef1bf80c6e919818fe6ff
- 作者: Russ Cox rsc@golang.org
- コミット日時: 2012年2月29日(水)16:37:40 -0500
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/fc268acf05adf5d0437ef1bf80c6e919818fe6ff
元コミット内容
path/filepath: steer people away from HasPrefix
The strikes against it are:
1. It does not take path boundaries into account.
2. It assumes that Windows==case-insensitive file system
and non-Windows==case-sensitive file system, neither of
which is always true.
3. Comparing ToLower against ToLower is not a correct
implementation of a case-insensitive string comparison.
4. If it returns true on Windows you still don't know how long
the matching prefix is in bytes, so you can't compute what
the suffix is.
R=golang-dev, r, dsymonds, r
CC=golang-dev
https://golang.org/cl/5712045
変更の背景
このコミットは、Go言語の標準ライブラリpath/filepath
パッケージに含まれるHasPrefix
関数の使用を非推奨にするための変更です。コミットメッセージには、この関数が抱える複数の問題点が具体的に挙げられており、それらがこの変更の主な背景となっています。
具体的には、以下の4つの問題点が指摘されています。
- パスの境界を考慮しない:
HasPrefix
は単なる文字列のプレフィックス比較を行うため、ファイルパスにおけるディレクトリの区切り(/
や\
)といった論理的な境界を考慮しません。例えば、/foo/bar
が/foo
で始まるかどうかは正しく判定できますが、/foobar
が/foo
で始まるかどうかを判定する際に、/foobar
が/foo
ディレクトリ内のファイルやディレクトリではないにも関わらずtrue
を返してしまう可能性があります。これは、ファイルパスの操作において予期せぬ結果を招く可能性があります。 - OSごとのファイルシステム特性の誤った仮定:
HasPrefix
は、Windowsファイルシステムは常に大文字・小文字を区別せず(case-insensitive)、非Windows(Unix系など)ファイルシステムは常に大文字・小文字を区別する(case-sensitive)という仮定に基づいていました。しかし、これは常に真ではありません。例えば、macOSのHFS+ファイルシステムはデフォルトで大文字・小文字を区別しませんし、Linuxでも特定のファイルシステム設定によっては大文字・小文字を区別しない場合があります。この誤った仮定は、クロスプラットフォームでの動作の不整合やバグの原因となります。 - 不正確な大文字・小文字を区別しない比較: 大文字・小文字を区別しない文字列比較を実装するために、両方の文字列を小文字に変換してから比較するという手法が用いられていました。しかし、これはUnicodeの複雑な文字変換ルール(特にトルコ語の
i
とI
のような特殊なケース)を考慮しておらず、正確な大文字・小文字を区別しない比較とは言えません。 - Windowsにおけるマッチングプレフィックスの長さの不明瞭さ: Windows環境で
HasPrefix
がtrue
を返した場合でも、マッチしたプレフィックスの正確なバイト長が不明瞭であるという問題がありました。これは、マッチしたプレフィックスを除いた残りのサフィックス部分を正確に計算することができないことを意味し、パス操作の柔軟性を損ないます。
これらの問題点から、HasPrefix
関数はファイルパスの操作において信頼性が低く、誤用を招きやすいと判断され、その使用を避けるように促す変更が導入されました。
前提知識の解説
このコミットを理解するためには、以下の概念について理解しておく必要があります。
-
Go言語の
path/filepath
パッケージ: Go言語の標準ライブラリの一部であり、オペレーティングシステムに依存しないパス操作機能を提供します。ファイルパスの結合、クリーンアップ、ディレクトリ名やファイル名の抽出、絶対パスへの変換など、様々なパス関連のユーティリティ関数が含まれています。このパッケージは、異なるOS(Unix、Windows、Plan 9など)のパス表現の違いを吸収し、統一的なインターフェースを提供することを目指しています。 -
strings.HasPrefix
関数: Go言語の標準ライブラリstrings
パッケージに含まれる関数で、ある文字列が指定されたプレフィックスで始まるかどうかを判定します。これは純粋な文字列比較であり、ファイルパスのセマンティクス(ディレクトリの区切りや大文字・小文字の区別など)は考慮しません。 -
ファイルシステムにおけるパスの概念: ファイルシステムは、ファイルやディレクトリを階層的に整理するための仕組みです。ファイルやディレクトリの場所は「パス」によって指定されます。パスの表現方法はオペレーティングシステムによって異なります。
- Unix系(Linux, macOSなど): パス区切り文字は
/
(スラッシュ)です。大文字・小文字を区別するファイルシステムが一般的です(例:file.txt
とFile.txt
は異なるファイル)。 - Windows: パス区切り文字は
\
(バックスラッシュ)です。ドライブレター(例:C:
)から始まる絶対パスが一般的です。ファイルシステムは通常、大文字・小文字を区別しません(例:file.txt
とFile.txt
は同じファイルとみなされることが多い)。 - Plan 9: Unix系に似ていますが、パスの概念やファイルシステムの設計思想に独自の特徴があります。
- Unix系(Linux, macOSなど): パス区切り文字は
-
大文字・小文字の区別(Case-sensitivity): ファイルシステムや文字列比較において、大文字と小文字を同じものとみなすか、異なるものとみなすかの特性です。
- Case-sensitive(大文字・小文字を区別する):
foo.txt
とFoo.txt
は異なるファイルとして扱われます。 - Case-insensitive(大文字・小文字を区別しない):
foo.txt
とFoo.txt
は同じファイルとして扱われます。
- Case-sensitive(大文字・小文字を区別する):
-
Unicodeと文字列比較: 現代のソフトウェアでは、様々な言語の文字を扱うためにUnicodeが広く用いられています。Unicodeには、同じ文字でも複数の表現形式があったり、大文字・小文字変換のルールが言語によって異なったりする複雑さがあります。単純な
ToLower
による比較では、これらの複雑なケースを正確に処理できない場合があります。
技術的詳細
このコミットがHasPrefix
関数の使用を非推奨にした技術的な理由は、前述の4つの問題点に深く関連しています。
-
パスの境界の無視:
path/filepath
パッケージの目的は、OSに依存しない形でファイルパスを安全かつ正確に操作することです。しかし、HasPrefix
は内部的にstrings.HasPrefix
を呼び出すだけであり、これは単なる文字列の字面上の比較に過ぎません。ファイルパスにおいては、/
や\
といったディレクトリセパレータが重要な意味を持ちます。例えば、/home/user/data
というパスと/home/user/database
というパスがあった場合、strings.HasPrefix("/home/user/database", "/home/user/data")
はtrue
を返しますが、これは/home/user/database
が/home/user/data
ディレクトリの「内部」にあることを意味しません。ファイルパスのプレフィックスを判定する際には、パスの構成要素(ディレクトリやファイル名)の境界を正確に認識し、それらが完全な要素として一致するかどうかを判断する必要があります。HasPrefix
はこのセマンティクスを欠いていたため、誤ったパス操作を引き起こす可能性がありました。 -
OSごとのファイルシステム特性の誤った仮定: Go言語はクロスプラットフォーム開発を強く意識しており、
path/filepath
パッケージもその哲学に基づいています。しかし、HasPrefix
はWindowsでは大文字・小文字を区別しない比較を試み、Unix系では区別する比較を行うという、OSの一般的な特性に基づいた実装になっていました。このアプローチは、特定のOSのファイルシステムが常にその特性を持つという誤った仮定に依存しています。- Windows: 確かにWindowsのNTFSファイルシステムはデフォルトで大文字・小文字を区別しませんが、WSL (Windows Subsystem for Linux) 環境や特定の共有フォルダ設定では大文字・小文字を区別する場合があります。
- Unix系: Linuxのext4やmacOSのAPFSはデフォルトで大文字・小文字を区別しますが、macOSのHFS+はデフォルトで大文字・小文字を区別しません(ただし、大文字・小文字を区別する設定も可能です)。 このような多様なファイルシステム特性を、単純なOS判定で一律に扱うことは、クロスプラットフォーム互換性を損ない、予期せぬバグを生み出す原因となります。
-
不正確な大文字・小文字を区別しない比較:
HasPrefix
のWindows実装では、大文字・小文字を区別しない比較のためにstrings.ToLower
を使用していました。しかし、Unicodeには大文字・小文字変換のルールが複雑な文字が存在します。例えば、トルコ語のi
(点付きの小文字i)とI
(点なしの大文字I)、そしてİ
(点付きの大文字I)とı
(点なしの小文字i)の関係は、英語のi
とI
とは異なります。単純なToLower
では、これらの言語固有のルールを正しく処理できず、国際化された環境でのパス比較において誤った結果を招く可能性があります。正確な大文字・小文字を区別しない比較には、Unicodeの正規化(Normalization)や言語固有のロケール設定を考慮した比較アルゴリズムが必要です。 -
Windowsにおけるマッチングプレフィックスの長さの不明瞭さ: Windowsのファイルシステムでは、パスの正規化や大文字・小文字の扱いが複雑になることがあります。
HasPrefix
がtrue
を返した場合でも、実際にマッチしたプレフィックスが元の文字列のどの部分に対応するのか、そのバイト長が明確でないという問題がありました。これは、例えばTrimPrefix
のような関数でプレフィックスを取り除いて残りのサフィックスを得る場合に、正確な操作ができないことを意味します。パス操作においては、文字列の長さだけでなく、そのセマンティクスに基づいた正確な位置情報が不可欠です。
これらの技術的な問題点から、HasPrefix
はpath/filepath
パッケージの意図する「OSに依存しない安全なパス操作」という目標に合致しないと判断され、非推奨とされました。開発者には、よりセマンティクスを考慮したパス操作関数(例: filepath.Rel
やfilepath.Walk
など、またはより低レベルなstrings
パッケージの関数を適切に組み合わせる)を使用することが推奨されます。
コアとなるコードの変更箇所
このコミットでは、src/pkg/path/filepath
ディレクトリ内の以下の3つのファイルが変更されています。
src/pkg/path/filepath/path_plan9.go
src/pkg/path/filepath/path_unix.go
src/pkg/path/filepath/path_windows.go
それぞれのファイルで、HasPrefix
関数のコメントが変更されています。
--- a/src/pkg/path/filepath/path_plan9.go
+++ b/src/pkg/path/filepath/path_plan9.go
@@ -17,7 +17,7 @@ func VolumeName(path string) string {
return ""
}
-// HasPrefix tests whether the path p begins with prefix.
+// HasPrefix exists for historical compatibility and should not be used.
func HasPrefix(p, prefix string) bool {
return strings.HasPrefix(p, prefix)
}
--- a/src/pkg/path/filepath/path_unix.go
+++ b/src/pkg/path/filepath/path_unix.go
@@ -19,7 +19,7 @@ func VolumeName(path string) string {
return ""
}
-// HasPrefix tests whether the path p begins with prefix.
+// HasPrefix exists for historical compatibility and should not be used.
func HasPrefix(p, prefix string) bool {
return strings.HasPrefix(p, prefix)
}
--- a/src/pkg/path/filepath/path_windows.go
+++ b/src/pkg/path/filepath/path_windows.go
@@ -67,8 +67,7 @@ func VolumeName(path string) (v string) {
return ""
}
-// HasPrefix tests whether the path p begins with prefix.
-// It ignores case while comparing.
+// HasPrefix exists for historical compatibility and should not be used.
func HasPrefix(p, prefix string) bool {
if strings.HasPrefix(p, prefix) {
return true
コアとなるコードの解説
変更は非常にシンプルで、HasPrefix
関数のドキュメンテーションコメントを修正し、この関数が「歴史的な互換性のために存在し、使用すべきではない」ことを明確に示しています。
-
path_plan9.go
とpath_unix.go
: これらのファイルでは、HasPrefix
関数の元のコメント// HasPrefix tests whether the path p begins with prefix.
が、// HasPrefix exists for historical compatibility and should not be used.
に変更されています。これは、Unix系およびPlan 9環境におけるパス操作において、この関数が推奨されないことを示しています。これらの環境では、HasPrefix
は単にstrings.HasPrefix
を呼び出すだけであり、パスのセマンティクスを考慮していません。 -
path_windows.go
: Windows環境では、ファイルシステムが大文字・小文字を区別しないことが多いため、HasPrefix
の元のコメントには// It ignores case while comparing.
という記述がありました。しかし、このコミットでは、この行も削除され、他のOSと同様に// HasPrefix exists for historical compatibility and should not be used.
というコメントに統一されています。これは、Windowsにおける大文字・小文字を区別しない比較の実装が不正確であること、およびパスの境界を考慮しないという根本的な問題があるため、この関数自体が非推奨であることを強調しています。
この変更は、関数の実装自体を変更するものではなく、その使用方法に関するガイダンスを明確にすることで、開発者がこの関数を誤って使用することを防ぐことを目的としています。これにより、path/filepath
パッケージの利用者が、より堅牢でクロスプラットフォーム互換性のあるパス操作を行うよう促されます。
関連リンク
- Go CL 5712045: https://golang.org/cl/5712045 このコミットの元となったGoのコードレビューシステム(Gerrit)上のチェンジリストです。コミットメッセージに記載されているリンクであり、この変更に関する議論や詳細な経緯を確認できます。
参考にした情報源リンク
-
Go言語の
path/filepath
パッケージのドキュメント: https://pkg.go.dev/path/filepathHasPrefix
関数を含むpath/filepath
パッケージの公式ドキュメントです。現在のバージョンでは、HasPrefix
は非推奨であることが明記されています。 -
Go言語の
strings
パッケージのドキュメント: https://pkg.go.dev/stringsstrings.HasPrefix
やstrings.ToLower
など、文字列操作に関する基本的な関数が定義されているパッケージの公式ドキュメントです。 -
Unicodeの正規化と大文字・小文字変換に関する情報: (一般的な情報源として)
- Unicode Standard Annex #15: Unicode Normalization Forms
- Unicode Standard Annex #29: Unicode Text Segmentation これらのドキュメントは、Unicodeにおける文字列の比較や変換の複雑さについて理解を深めるのに役立ちます。
-
ファイルシステムの大文字・小文字の区別に関する情報: (一般的な情報源として)
- Wikipedia: Filename case-sensitivity 異なるオペレーティングシステムやファイルシステムにおける大文字・小文字の区別の特性について概説されています。