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

[インデックス 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らしいインターフェースベースのアプローチに移行することです。

  1. 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固有の型に型アサーションを行うことで、詳細な情報にアクセスできます。

  2. FileStatの非公開化とfileStatへのリネーム: 以前はFileStatという公開された構造体が存在し、そのSysフィールドも公開されていました。これは、外部のコードがFileStatの内部実装に直接依存する可能性を秘めていました。このコミットでは、FileStatfileStatという非公開の構造体にリネームし、そのSysフィールドもsysという非公開のフィールドに変更しました。 これにより、osパッケージの外部からはfileStat構造体の存在やその内部実装が見えなくなり、osパッケージの内部でのみ管理されるようになります。これは、ソフトウェア設計における「情報隠蔽」の原則に従っており、将来的な内部実装の変更が外部のコードに影響を与えるリスクを大幅に低減します。

  3. 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を提供することを目的としています。

関連リンク

参考にした情報源リンク