[インデックス 11577] ファイルの概要
コミット
commit 20f4385af0690b6f1c7a0ba5380f0b057a87485d
Author: Gustavo Niemeyer <gustavo@niemeyer.net>
Date: Fri Feb 3 00:16:18 2012 -0200
os: turn FileStat.Sys into a method on FileInfo
This reduces the overhead necessary to work with OS-specific
file details, hides the implementation of FileStat, and
preserves the implementation-specific nature of Sys.
Expressions such as:
stat.(*os.FileInfo).Sys.(*syscall.Stat_t).Uid
fi1.(*os.FileStat).SameFile(fi2.(*os.FileStat))
Are now spelled as::
stat.Sys().(*syscall.Stat_t).Uid
os.SameFile(fi1, fi2)
R=cw, bradfitz, rsc
CC=golang-dev
https://golang.org/cl/5448079
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/20f4385af0690b6f1c7a0ba5380f0b057a87485d
元コミット内容
このコミットは、Go言語のos
パッケージにおけるファイル情報の扱い方を改善することを目的としています。具体的には、FileStat.Sys
フィールドをFileInfo
インターフェースのメソッドに変換し、os.SameFile
関数を導入しています。
変更の要点は以下の通りです。
FileStat.Sys
フィールドをFileInfo
インターフェースのSys() interface{}
メソッドに変更。FileStat
の実装詳細を隠蔽し、OS固有のファイル詳細を扱う際のオーバーヘッドを削減。SameFile
メソッドをos.SameFile
関数として公開し、FileInfo
インターフェースを引数に取るように変更。
これにより、以下のようなコードの記述方法が変更されます。
-
変更前:
stat.(*os.FileInfo).Sys.(*syscall.Stat_t).Uid
-
変更後:
stat.Sys().(*syscall.Stat_t).Uid
-
変更前:
fi1.(*os.FileStat).SameFile(fi2.(*os.FileStat))
-
変更後:
os.SameFile(fi1, fi2)
変更の背景
この変更の背景には、Go言語の標準ライブラリにおけるインターフェースの設計思想と、OS固有の情報をより抽象的に扱う必要性があります。
Go言語では、インターフェースは振る舞いを定義し、具体的な実装の詳細を隠蔽することを推奨しています。os.FileInfo
インターフェースは、ファイルに関する一般的な情報(名前、サイズ、モード、更新時刻など)を提供する役割を担っています。しかし、FileStat
構造体が直接Sys
フィールドを持つことで、OS固有の低レベルなファイルシステム情報(inode番号、デバイスIDなど)にアクセスする際に、具体的なFileStat
型への型アサーションが必要となり、インターフェースの抽象化が損なわれる可能性がありました。
また、SameFile
メソッドも*os.FileStat
型に紐付いていたため、FileInfo
インターフェースを介してファイルの同一性を比較する際に、やはり型アサーションが必要でした。
このコミットは、これらの問題を解決し、os
パッケージのAPIをよりGoらしい(idiomaticな)ものにすることを目的としています。Sys()
メソッドをFileInfo
インターフェースに含めることで、OS固有の情報へのアクセスをインターフェースの振る舞いの一部として定義し、具体的な実装の詳細を隠蔽します。また、os.SameFile
関数を導入することで、FileInfo
インターフェースを介してファイルの同一性を比較できるようになり、コードの柔軟性と可読性が向上します。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とファイルシステムに関する基本的な知識が必要です。
Go言語のインターフェース (Interface)
Go言語のインターフェースは、メソッドのシグネチャの集まりです。型がインターフェースのすべてのメソッドを実装していれば、そのインターフェースを満たすと見なされます(暗黙的なインターフェースの実装)。これにより、具体的な型に依存せずに、共通の振る舞いを定義し、ポリモーフィズムを実現できます。os.FileInfo
は、ファイルに関する情報を提供するインターフェースです。
型アサーション (Type Assertion)
Go言語では、インターフェース型の変数が保持している具体的な型を、実行時に確認し、その具体的な型として扱うために型アサーションを使用します。例えば、value.(ConcreteType)
のように記述します。このコミットの変更前は、FileInfo
インターフェースからOS固有の情報を取得するために、(*os.FileStat)
への型アサーションが必要でした。
syscall
パッケージ
syscall
パッケージは、Goプログラムからオペレーティングシステムのシステムコールにアクセスするための低レベルなインターフェースを提供します。ファイルシステムに関する詳細な情報(inode番号、デバイスID、UID、GIDなど)は、通常、syscall
パッケージを通じて取得されるStat_t
などの構造体に格納されています。
os.FileInfo
インターフェース
os.FileInfo
インターフェースは、Go言語でファイルやディレクトリのメタデータ(名前、サイズ、パーミッション、更新時刻、ディレクトリかどうかなど)を抽象的に扱うための標準的なインターフェースです。
os.FileStat
構造体 (変更前)
変更前のos
パッケージには、FileStat
という具体的な構造体が存在し、これがos.FileInfo
インターフェースを実装していました。このFileStat
構造体には、OS固有のファイルシステム情報を含むSys interface{}
フィールドがありました。
inode (Index Node)
Unix系ファイルシステムにおけるinodeは、ファイルやディレクトリに関するメタデータ(所有者、パーミッション、タイムスタンプ、データブロックへのポインタなど)を格納するデータ構造です。各ファイルやディレクトリは一意のinode番号を持ちます。
デバイスID (Device ID)
ファイルが存在するファイルシステム(デバイス)を識別するためのIDです。異なるデバイス上のファイルは、たとえinode番号が同じでも、異なるファイルと見なされます。
SameFile
の概念
ファイルシステムにおいて、2つのファイルが「同じファイル」であると判断されるのは、通常、それらが同じデバイス上の同じinodeを指している場合です。これは、ハードリンクによって同じファイルが複数のパスから参照される場合に特に重要になります。
技術的詳細
このコミットの技術的な核心は、os.FileInfo
インターフェースの拡張と、os.FileStat
構造体の内部実装の変更、そしてos.SameFile
関数の導入にあります。
FileInfo
インターフェースへのSys()
メソッドの追加
最も重要な変更は、src/pkg/os/types.go
においてFileInfo
インターフェースにSys() interface{}
メソッドが追加されたことです。
type FileInfo interface {
Name() string // base name of the file
Size() int64 // length in bytes
Mode() FileMode // file mode bits
ModTime() time.Time // modification time
IsDir() bool // abbreviation for Mode().IsDir()
Sys() interface{} // underlying data source (can return nil)
}
この変更により、FileInfo
インターフェースを実装するすべての型は、Sys()
メソッドを提供することが義務付けられます。このメソッドは、OS固有のファイルシステム情報を含むinterface{}
型の値を返します。これにより、呼び出し側は具体的なFileStat
型に依存することなく、FileInfo
インターフェースを通じてOS固有の情報にアクセスできるようになります。
FileStat
構造体の変更とfileStat
へのリネーム
src/pkg/os/types.go
では、公開されていたFileStat
構造体が非公開のfileStat
構造体へとリネームされ、そのSys
フィールドも非公開のsys
フィールドに変更されました。
// A FileStat is the implementation of FileInfo returned by Stat and Lstat.
// Clients that need access to the underlying system-specific stat information
// can test for *os.FileStat and then consult the Sys field.
-type FileStat struct {
+type fileStat struct {
name string
size int64
mode FileMode
modTime time.Time
-
- Sys interface{}
+ sys interface{}
}
そして、fileStat
構造体はFileInfo
インターフェースの新しいSys()
メソッドを実装します。
func (fs *fileStat) Sys() interface{} { return fs.sys }
この変更により、FileStat
の実装詳細が外部から隠蔽され、os
パッケージの内部でのみ管理されるようになります。これにより、将来的な内部実装の変更が外部のコードに影響を与えるリスクが低減されます。
os.SameFile
関数の導入
src/pkg/os/types.go
では、FileStat
構造体のメソッドであったSameFile
が、os
パッケージのトップレベル関数SameFile(fi1, fi2 FileInfo)
として再定義されました。
// SameFile reports whether fi1 and fi2 describe the same file.
// For example, on Unix this means that the device and inode fields
// of the two underlying structures are identical; on other systems
// the decision may be based on the path names.
// SameFile only applies to results returned by this package's Stat.
// It returns false in other cases.
func SameFile(fi1, fi2 FileInfo) bool {
fs1, ok1 := fi1.(*fileStat)
fs2, ok2 := fi2.(*fileStat)
if !ok1 || !ok2 {
return false
}
return sameFile(fs1.sys, fs2.sys)
}
この関数は、2つのFileInfo
インターフェースを受け取り、それらが同じファイルを参照しているかどうかを報告します。内部的には、fileStat
型への型アサーションを行い、そのsys
フィールド(OS固有のデータ)を比較することでファイルの同一性を判断します。これにより、FileInfo
インターフェースを扱うコードが、ファイルの同一性チェックをより簡潔に行えるようになります。
各OS固有のstat_*.go
ファイルの変更
src/pkg/os/stat_darwin.go
, src/pkg/os/stat_freebsd.go
, src/pkg/os/stat_linux.go
, src/pkg/os/stat_netbsd.go
, src/pkg/os/stat_openbsd.go
, src/pkg/os/stat_plan9.go
, src/pkg/os/stat_windows.go
といった各OS固有のファイルでは、fileInfoFromStat
関数がFileStat
の代わりにfileStat
を返すように変更され、Sys
フィールドへの直接アクセスがsys
フィールドへのアクセスに置き換えられました。また、sameFile
ヘルパー関数も、*FileStat
を引数に取る代わりに、interface{}
を引数に取るように変更され、内部で適切な型アサーションを行うようになりました。
これらの変更により、OS固有のファイル情報へのアクセスが、FileInfo
インターフェースのSys()
メソッドを介して統一的に行われるようになり、コードの整合性が向上しました。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、主にsrc/pkg/os/types.go
と、各OS固有のsrc/pkg/os/stat_*.go
ファイルに集中しています。
src/pkg/os/types.go
FileInfo
インターフェースにSys() interface{}
メソッドが追加されました。FileStat
構造体がfileStat
にリネームされ、非公開化されました。fileStat
構造体にSys() interface{}
メソッドの実装が追加されました。SameFile
メソッドが削除され、os.SameFile
関数が追加されました。
--- a/src/pkg/os/types.go
+++ b/src/pkg/os/types.go
@@ -19,6 +19,7 @@ type FileInfo interface {
Mode() FileMode // file mode bits
ModTime() time.Time // modification time
IsDir() bool // abbreviation for Mode().IsDir()
+ Sys() interface{} // underlying data source (can return nil)
}
// A FileMode represents a file's mode and permission bits.
@@ -92,28 +93,33 @@ func (m FileMode) Perm() FileMode {
return m & ModePerm
}
-// A FileStat is the implementation of FileInfo returned by Stat and Lstat.
-// Clients that need access to the underlying system-specific stat information
-// can test for *os.FileStat and then consult the Sys field.
-type FileStat struct {
+// A fileStat is the implementation of FileInfo returned by Stat and Lstat.
+type fileStat struct {
name string
size int64
mode FileMode
modTime time.Time
-
- Sys interface{}
+ sys interface{}
}
-func (fs *FileStat) Name() string { return fs.name }
-func (fs *FileStat) Size() int64 { return fs.size }
-func (fs *FileStat) Mode() FileMode { return fs.mode }
-func (fs *FileStat) ModTime() time.Time { return fs.modTime }
-func (fs *FileStat) IsDir() bool { return fs.mode.IsDir() }
+func (fs *fileStat) Name() string { return fs.name }
+func (fs *fileStat) Size() int64 { return fs.size }
+func (fs *fileStat) Mode() FileMode { return fs.mode }
+func (fs *fileStat) ModTime() time.Time { return fs.modTime }
+func (fs *fileStat) IsDir() bool { return fs.mode.IsDir() }
+func (fs *fileStat) Sys() interface{} { return fs.sys }
-// SameFile reports whether fs and other describe the same file.
+// SameFile reports whether fi1 and fi2 describe the same file.
// For example, on Unix this means that the device and inode fields
// of the two underlying structures are identical; on other systems
// the decision may be based on the path names.
-func (fs *FileStat) SameFile(other *FileStat) bool {\n-\treturn sameFile(fs, other)\n+// SameFile only applies to results returned by this package's Stat.
+// It returns false in other cases.
+func SameFile(fi1, fi2 FileInfo) bool {
+ fs1, ok1 := fi1.(*fileStat)
+ fs2, ok2 := fi2.(*fileStat)
+ if !ok1 || !ok2 {
+ return false
+ }
+ return sameFile(fs1.sys, fs2.sys)
}
src/pkg/os/stat_darwin.go
(他のOS固有ファイルも同様の変更)
sameFile
関数が*FileStat
を引数に取る代わりにinterface{}
を引数に取るように変更されました。fileInfoFromStat
関数が&FileStat
の代わりに&fileStat
を返すように変更され、Sys
フィールドへの代入がsys
フィールドへの代入に置き換えられました。atime
ヘルパー関数がfi.(*FileStat).Sys
の代わりにfi.Sys()
を使用するように変更されました。
--- a/src/pkg/os/stat_darwin.go
+++ b/src/pkg/os/stat_darwin.go
@@ -9,18 +9,18 @@ import (
"time"
)
-func sameFile(fs1, fs2 *FileStat) bool {
- sys1 := fs1.Sys.(*syscall.Stat_t)
- sys2 := fs2.Sys.(*syscall.Stat_t)
- return sys1.Dev == sys2.Dev && sys1.Ino == sys2.Ino
+func sameFile(sys1, sys2 interface{}) bool {
+ stat1 := sys1.(*syscall.Stat_t)
+ stat2 := sys2.(*syscall.Stat_t)
+ return stat1.Dev == stat2.Dev && stat1.Ino == stat2.Ino
}
func fileInfoFromStat(st *syscall.Stat_t, name string) FileInfo {
- fs := &FileStat{
+ fs := &fileStat{
name: basename(name),
size: int64(st.Size),
modTime: timespecToTime(st.Mtimespec),
- Sys: st,
+ sys: st,
}
fs.mode = FileMode(st.Mode & 0777)
switch st.Mode & syscall.S_IFMT {
@@ -57,5 +57,5 @@ func timespecToTime(ts syscall.Timespec) time.Time {
// For testing.
func atime(fi FileInfo) time.Time {
- return timespecToTime(fi.(*FileStat).Sys.(*syscall.Stat_t).Atimespec)
+ return timespecToTime(fi.Sys().(*syscall.Stat_t).Atimespec)
}
コアとなるコードの解説
このコミットの目的は、Goのos
パッケージにおけるファイル情報の扱いを、よりGoらしいインターフェースベースのアプローチに移行することです。
-
FileInfo
インターフェースの拡張: 以前は、OS固有のファイルシステム情報(例えばUnix系OSのinode番号やデバイスID)にアクセスするには、os.FileInfo
インターフェースを実装している具体的な型(通常は*os.FileStat
)に型アサーションを行い、そのSys
フィールドにアクセスする必要がありました。// 変更前: stat.(*os.FileInfo).Sys.(*syscall.Stat_t).Uid
このアプローチは、インターフェースの抽象化を破り、具体的な実装の詳細に依存することになります。このコミットでは、
FileInfo
インターフェース自体にSys() interface{}
メソッドを追加することで、この問題を解決しました。// 変更後: stat.Sys().(*syscall.Stat_t).Uid
これにより、
FileInfo
インターフェースを扱うコードは、具体的な型を知らなくてもSys()
メソッドを呼び出すことができ、より柔軟で抽象的なコードを書くことが可能になります。Sys()
メソッドが返すinterface{}
は、呼び出し側がOS固有の型に型アサーションを行うことで、詳細な情報にアクセスできます。 -
FileStat
の非公開化とfileStat
へのリネーム: 以前はFileStat
という公開された構造体が存在し、そのSys
フィールドも公開されていました。これは、外部のコードがFileStat
の内部実装に直接依存する可能性を秘めていました。このコミットでは、FileStat
をfileStat
という非公開の構造体にリネームし、そのSys
フィールドもsys
という非公開のフィールドに変更しました。 これにより、os
パッケージの外部からはfileStat
構造体の存在やその内部実装が見えなくなり、os
パッケージの内部でのみ管理されるようになります。これは、ソフトウェア設計における「情報隠蔽」の原則に従っており、将来的な内部実装の変更が外部のコードに影響を与えるリスクを大幅に低減します。 -
os.SameFile
関数の導入: ファイルの同一性を比較するSameFile
機能も、以前は*os.FileStat
型のメソッドとして提供されていました。// 変更前: fi1.(*os.FileStat).SameFile(fi2.(*os.FileStat))
これもまた、
FileInfo
インターフェースの抽象化を破るものでした。このコミットでは、os.SameFile(fi1, fi2 FileInfo)
というトップレベル関数を導入しました。// 変更後: os.SameFile(fi1, fi2)
この関数は、2つの
FileInfo
インターフェースを引数として受け取り、内部でfileStat
型への型アサーションを行い、そのsys
フィールド(OS固有のデータ)を比較することでファイルの同一性を判断します。これにより、FileInfo
インターフェースを扱うコードが、具体的な型に依存せずにファイルの同一性チェックを行えるようになり、APIの一貫性が向上しました。
これらの変更は、Go言語の設計哲学である「シンプルさ」と「明示性」を追求し、より堅牢で保守性の高いAPIを提供することを目的としています。
関連リンク
- Go言語の
os
パッケージのドキュメント: https://pkg.go.dev/os - Go言語のインターフェースに関する公式ドキュメント: https://go.dev/tour/methods/10
- Go言語の型アサーションに関する公式ドキュメント: https://go.dev/tour/methods/15
参考にした情報源リンク
- https://golang.org/cl/5448079 (元のGerritチェンジリスト)
- https://github.com/golang/go/commit/20f4385af0690b6f1c7a0ba5380f0b057a87485d (GitHub上のコミットページ)
- Go言語の
syscall
パッケージのドキュメント: https://pkg.go.dev/syscall - Unix系ファイルシステムのinodeに関する情報 (一般的な情報源): https://ja.wikipedia.org/wiki/Inode
- Go言語の設計原則に関する一般的な情報 (例: Effective Go): https://go.dev/doc/effective_go