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

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

このコミットは、Go言語の標準ライブラリosパッケージにおけるファイル操作のAPIを大幅に改善するものです。具体的には、ファイルディスクリプタを表す型名をos.FDからos.Fileへと変更し、さらにFstatReaddirnamesReaddirといったファイル関連のグローバル関数をos.File型のメソッドとして再定義しています。これにより、ファイル操作がよりオブジェクト指向的かつ直感的なAPIに統一され、コードの可読性と保守性が向上しています。

コミット

commit 7a706fb3d7642e782f60d3d1d137b3c220643b46
Author: Russ Cox <rsc@golang.org>
Date:   Wed Mar 11 12:51:10 2009 -0700

    Rename os.FD to os.File.
    
    Make Fstat, Readdirnames, and Readdir methods
    on os.File.
    
    R=r
    DELTA=281  (79 added, 3 deleted, 199 changed)
    OCL=25891
    CL=26130

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

https://github.com/golang/go/commit/7a706fb3d7642e782f60d3d1d137b3c220643b46

元コミット内容

このコミットは、Go言語のosパッケージにおけるファイル操作の基本的な型と関数に焦点を当てたものです。主な変更点は以下の通りです。

  1. os.FDからos.Fileへの型名変更: これまでファイルディスクリプタを抽象化していたos.FD型がos.Fileへと名称変更されました。これは、より直感的で、ファイルという概念を直接的に表す名前にすることで、APIの意図を明確にする狙いがあります。
  2. 関連関数のメソッド化: FstatReaddirnamesReaddirといったファイルに関する操作を行う関数が、os.File型のメソッドとして再定義されました。これにより、これらの操作が特定のFileインスタンスに紐付けられ、よりオブジェクト指向的なスタイルでファイル操作を行えるようになります。

これらの変更は、Go言語の初期段階におけるAPI設計の洗練化の一環であり、より一貫性のある、使いやすい標準ライブラリを目指す方向性を示しています。

変更の背景

この変更の背景には、Go言語のAPI設計哲学と、より直感的で「Goらしい」コードスタイルへの移行があります。初期のGo言語では、C言語の影響を強く受けたAPI設計が見られ、ファイルディスクリプタ(FD)という低レベルな概念が直接的に露出していました。しかし、Go言語が成熟するにつれて、より高レベルで抽象化されたAPIが求められるようになりました。

  • APIの直感性向上: FDという名称は、Unix系のシステムプログラミングに慣れた開発者には馴染み深いものですが、Go言語のユーザーベースが広がるにつれて、より汎用的で理解しやすい「ファイル」という概念を直接的に表すFileという名称が適切であると判断されました。
  • オブジェクト指向的なアプローチの採用: Fstat(ファイルの状態取得)、Readdirnames(ディレクトリ内のエントリ名取得)、Readdir(ディレクトリ内のエントリ情報取得)といった操作は、特定の開かれたファイルやディレクトリに対して行われるものです。これらをグローバル関数として提供するよりも、os.File型のメソッドとして提供することで、操作対象が明確になり、コードの可読性と自己文書化能力が向上します。例えば、Fstat(fd)と書くよりもfile.Stat()と書く方が、fileというオブジェクトに対してStat操作を行っていることが一目瞭然です。
  • 一貫性の確保: Go言語の標準ライブラリ全体で、関連する操作を構造体のメソッドとして提供するパターンが確立されつつありました。この変更は、osパッケージもこのパターンに準拠させ、ライブラリ全体の一貫性を高めることを目的としています。

これらの変更は、Go言語が低レベルなシステムプログラミングの能力を維持しつつも、より現代的で使いやすい高レベルなプログラミング言語としての地位を確立していく過程で不可欠なステップでした。

前提知識の解説

このコミットの変更内容を理解するためには、以下の概念について基本的な知識が必要です。

1. ファイルディスクリプタ (File Descriptor, FD)

  • 定義: Unix系オペレーティングシステムにおいて、プロセスが開いているファイルやI/Oリソース(パイプ、ソケットなど)を識別するために使用される非負の整数値です。
  • 役割: プログラムがファイルシステムやI/Oデバイスとやり取りする際の抽象的なハンドルとして機能します。例えば、open()システムコールはファイルディスクリプタを返し、その後のread()write()システムコールはそのディスクリプタを使って操作対象を指定します。
  • 標準ファイルディスクリプタ:
    • 0: 標準入力 (stdin)
    • 1: 標準出力 (stdout)
    • 2: 標準エラー出力 (stderr)

2. Go言語のosパッケージ

  • 役割: Go言語の標準ライブラリの一部であり、オペレーティングシステム(OS)の機能へのプラットフォーム非依存なインターフェースを提供します。ファイルシステム操作(ファイルの作成、読み書き、削除、ディレクトリ操作など)、プロセス管理、環境変数へのアクセスなどが含まれます。
  • 初期の設計: Go言語の初期段階では、osパッケージはファイルディスクリプタを直接扱うos.FD型を提供していました。これは、低レベルなシステムコールへの薄いラッパーとして機能していました。

3. Go言語のメソッド (Methods)

  • 定義: Go言語において、特定の型(構造体やカスタム型)に関連付けられた関数です。メソッドは、その型のインスタンスに対して操作を実行するために使用されます。
  • 構文: func (receiver Type) MethodName(parameters) (results) { ... }
    • receiver: メソッドが関連付けられる型のインスタンス。慣習的に、ポインタレシーバ(*Type)または値レシーバ(Type)を使用します。
  • 利点:
    • カプセル化: データ(レシーバのフィールド)と、そのデータを操作するロジック(メソッド)を一緒に定義できます。
    • 可読性: object.Method()という形式で呼び出すことで、どのオブジェクトに対して操作が行われているかが明確になります。
    • インターフェースの実装: メソッドを持つことで、その型が特定のインターフェースを満たすことを宣言できます。

4. Go言語のインターフェース (Interfaces)

  • 定義: Go言語において、メソッドのシグネチャの集合を定義する型です。インターフェースは、特定の振る舞いを抽象化するために使用されます。
  • 特徴: Goのインターフェースは「暗黙的」に実装されます。つまり、ある型がインターフェースで定義されたすべてのメソッドを実装していれば、その型はそのインターフェースを満たしているとみなされます。明示的なimplementsキーワードは不要です。
  • io.Reader, io.Writer, io.Closer, io.ReadWriteCloser: ioパッケージで定義されているこれらのインターフェースは、Go言語におけるI/O操作の基本的な抽象化を提供します。
    • io.Reader: Read(p []byte) (n int, err error)メソッドを持つ。
    • io.Writer: Write(p []byte) (n int, err error)メソッドを持つ。
    • io.Closer: Close() errorメソッドを持つ。
    • io.ReadWriteCloser: io.Reader, io.Writer, io.Closerのすべてのメソッドを持つ。

このコミットは、os.FDio.ReadWriteCloserインターフェースを実装していたのと同様に、os.Fileもこれらのインターフェースを引き続き実装することで、既存のI/O関連コードとの互換性を維持しつつ、APIのセマンティクスを向上させています。

技術的詳細

このコミットの技術的な詳細を掘り下げると、Go言語のAPI設計における重要な進化が見て取れます。

1. os.FDからos.Fileへの型名変更

  • 変更点: src/lib/os/file.goにおいて、type FD struct { ... }type File struct { ... }に変更されました。これに伴い、NewFD関数はNewFileに、Stdin, Stdout, Stderrといったグローバル変数もNewFileを使って初期化されるようになりました。
  • 影響:
    • セマンティクスの明確化: FDは「ファイルディスクリプタ」という低レベルな概念を指しますが、Fileは「ファイル」というより高レベルで一般的な概念を指します。この変更により、osパッケージが提供する抽象化レベルがより明確になりました。ユーザーは、OSの内部的なファイルディスクリプタを直接意識することなく、ファイルというオブジェクトとして操作できるようになります。
    • コードの可読性向上: *os.FDという型よりも*os.Fileの方が、それがファイルオブジェクトへのポインタであることが直感的に理解できます。
    • 既存コードへの波及: os.FDを使用していたすべての箇所(exechttpnetlogstrconvtimeパッケージなど)で、型名の変更とそれに伴う変数名の変更(例: fdからfileosfdからfile)が必要となりました。これは、Goの静的型付けの恩恵により、コンパイル時に変更箇所が特定されやすいという側面もあります。

2. Fstat, Readdirnames, Readdirのメソッド化

  • 変更点:
    • func Fstat(fd *FD) (dir *Dir, err *Error)func (file *File) Stat() (dir *Dir, err *Error)に変更されました。
    • func Readdirnames(fd *FD, count int) (names []string, err *Error)func (file *File) Readdirnames(count int) (names []string, err *Error)に変更されました。
    • func Readdir(fd *FD, count int) (dirs []Dir, err *Error)func (file *File) Readdir(count int) (dirs []Dir, err *Error)に変更されました。
  • 影響:
    • オブジェクト指向的なAPI: これらの関数は、特定の開かれたファイルやディレクトリの状態を取得したり、内容を読み取ったりする操作です。これらをos.Fileのメソッドとすることで、操作対象がFileインスタンス自身であることが明確になり、file.Stat()のように自然な呼び出しが可能になります。これは、Go言語が推奨する「レシーバを持つ関数」のパターンに合致します。
    • カプセル化の強化: File構造体内部のfd(ファイルディスクリプタの整数値)やdirinfo(ディレクトリ情報)といったフィールドは、外部から直接アクセスされるべきではありません。メソッドを通じてこれらの内部状態を操作することで、APIの利用者は実装の詳細を意識することなく、高レベルなファイル操作に集中できます。
    • インターフェースとの整合性: os.Fileio.Readerio.Writerio.Closerといった基本的なI/Oインターフェースを実装しています。今回の変更により、StatReaddirnamesといったファイルシステム固有の操作もFile型に集約され、ファイル関連の機能がより一貫した形で提供されるようになりました。
    • os.Openの戻り値: os.Open関数も、*os.FDではなく*os.Fileを返すように変更されました。これにより、ファイルを開いた直後にそのFileインスタンスに対して直接メソッドを呼び出すことができるようになり、コードの簡潔性が向上します。

3. その他の影響箇所

  • src/lib/exec.go: プロセス実行時の標準入出力エラーを扱うCmd構造体のフィールドが*os.FDから*os.Fileに変更され、関連するヘルパー関数modeToFDsmodeToFilesにリネームされました。
  • src/lib/http/server.go: HTTPコネクションを表すConn構造体内のI/O接続を表すフィールドがfd io.ReadWriteCloseからrwc io.ReadWriteCloseに変更され、それに伴いc.fdへのアクセスがc.rwcに変更されました。これは、os.Fileio.ReadWriteCloseインターフェースを満たすため、より汎用的なインターフェース型を使用することで、HTTPサーバーが特定のファイルディスクリプタ実装に依存しないようにする変更です。
  • src/lib/net/fd.go: ネットワークファイルディスクリプタを扱うnetFD構造体内のosfd *os.FDフィールドがfile *os.Fileに変更されました。これは、ネットワークI/OもファイルI/Oと同様にos.Fileの抽象化の下で扱われることを示しています。
  • src/lib/log_test.go, src/lib/strconv/fp_test.go, src/lib/time/zoneinfo.go: これらのテストファイルやユーティリティファイルでも、os.Openの戻り値やos.Pipeの戻り値が*os.File型として扱われるように修正されています。

これらの変更は、Go言語の標準ライブラリが、より抽象的で、使いやすく、そして「Goらしい」APIデザインへと進化していく過程における重要なマイルストーンを示しています。低レベルなOSの概念を適切に抽象化し、開発者がより高レベルなアプリケーションロジックに集中できるようにするための努力が反映されています。

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

このコミットのコアとなる変更は、src/lib/os/file.goに集中しています。

src/lib/os/file.go

--- a/src/lib/os/file.go
+++ b/src/lib/os/file.go
@@ -11,45 +11,44 @@ import (
 	"syscall";
 )
 
-// Auxiliary information if the FD describes a directory
+// Auxiliary information if the File describes a directory
 type dirInfo struct {	// TODO(r): 6g bug means this can't be private
 	buf	[]byte;	// buffer for directory I/O
 	nbuf	int64;	// length of buf; return value from Getdirentries
 	bufp	int64;	// location of next record in buf.
 }
 
-// FD represents an open file.
-// TODO(r): is FD the right name? Would File be better?
-type FD struct {
+// File represents an open file descriptor.
+type File struct {
 	fd int64;
 	name	string;
 	dirinfo	*dirInfo;	// nil unless directory being read
 }
 
 // Fd returns the integer Unix file descriptor referencing the open file.
-func (fd *FD) Fd() int64 {
-	return fd.fd
+func (file *File) Fd() int64 {
+	return file.fd
 }
 
 // Name returns the name of the file as presented to Open.
-func (fd *FD) Name() string {
-	return fd.name
+func (file *File) Name() string {
+	return file.name
 }
 
-// NewFD returns a new FD with the given file descriptor and name.
-func NewFD(fd int64, name string) *FD {
-	if fd < 0 {
+// NewFile returns a new File with the given file descriptor and name.
+func NewFile(file int64, name string) *File {
+	if file < 0 {
 		return nil
 	}
-	return &FD{fd, name, nil}
+	return &File{file, name, nil}
 }
 
-// Stdin, Stdout, and Stderr are open FDs pointing to the standard input,
+// Stdin, Stdout, and Stderr are open Files pointing to the standard input,
 // standard output, and standard error file descriptors.
 var (
-	Stdin = NewFD(0, "/dev/stdin");
-	Stdout = NewFD(1, "/dev/stdout");
-	Stderr = NewFD(2, "/dev/stderr");
+	Stdin  = NewFile(0, "/dev/stdin");
+	Stdout = NewFile(1, "/dev/stdout");
+	Stderr = NewFile(2, "/dev/stderr");
 )
 
 // Flags to Open wrapping those of the underlying system. Not all flags
@@ -69,9 +68,9 @@ const (
 )
 
 // Open opens the named file with specified flag (O_RDONLY etc.) and perm, (0666 etc.)
-// if applicable.  If successful, methods on the returned FD can be used for I/O.\n// It returns the FD and an Error, if any.\n-func Open(name string, flag int, perm int) (fd *FD, err *Error) {
+// if applicable.  If successful, methods on the returned File can be used for I/O.
+// It returns the File and an Error, if any.
+func Open(name string, flag int, perm int) (file *File, err *Error) {
 	r, e := syscall.Open(name, int64(flag | syscall.O_CLOEXEC), int64(perm));
 	if e != 0 {
 		return nil, ErrnoToError(e);
@@ -83,31 +82,31 @@ func Open(name string, flag int, perm int) (fd *FD, err *Error) {
 		syscall.CloseOnExec(r);
 	}
 
-	return NewFD(r, name), ErrnoToError(e)
+	return NewFile(r, name), ErrnoToError(e)
 }
 
-// Close closes the FD, rendering it unusable for I/O.
+// Close closes the File, rendering it unusable for I/O.
 // It returns an Error, if any.
-func (fd *FD) Close() *Error {
-	if fd == nil {
+func (file *File) Close() *Error {
+	if file == nil {
 		return EINVAL
 	}
-	r, e := syscall.Close(fd.fd);
-	fd.fd = -1;  // so it can't be closed again
+	r, e := syscall.Close(file.fd);
+	file.fd = -1;  // so it can't be closed again
 	return ErrnoToError(e)
 }
 
-// Read reads up to len(b) bytes from the FD.
+// Read reads up to len(b) bytes from the File.
 // It returns the number of bytes read and an Error, if any.
 // EOF is signaled by a zero count with a nil Error.
 // TODO(r): Add Pread, Pwrite (maybe ReadAt, WriteAt).
-func (fd *FD) Read(b []byte) (ret int, err *Error) {
-	if fd == nil {
+func (file *File) Read(b []byte) (ret int, err *Error) {
+	if file == nil {
 		return 0, EINVAL
 	}
 	var r, e int64;
 	if len(b) > 0 {  // because we access b[0]
-		r, e = syscall.Read(fd.fd, &b[0], int64(len(b)));
+		r, e = syscall.Read(file.fd, &b[0], int64(len(b)));
 		if r < 0 {
 			r = 0
 		}
@@ -115,16 +114,16 @@ func (fd *FD) Read(b []byte) (ret int, err *Error) {
 	return int(r), ErrnoToError(e)
 }
 
-// Write writes len(b) bytes to the FD.
+// Write writes len(b) bytes to the File.
 // It returns the number of bytes written and an Error, if any.
 // If the byte count differs from len(b), it usually implies an error occurred.
-func (fd *FD) Write(b []byte) (ret int, err *Error) {
-	if fd == nil {
+func (file *File) Write(b []byte) (ret int, err *Error) {
+	if file == nil {
 		return 0, EINVAL
 	}
 	var r, e int64;
 	if len(b) > 0 {  // because we access b[0]
-		r, e = syscall.Write(fd.fd, &b[0], int64(len(b)));
+		r, e = syscall.Write(file.fd, &b[0], int64(len(b)));
 		if r < 0 {
 			r = 0
 		}
@@ -132,16 +131,16 @@ func (fd *FD) Write(b []byte) (ret int, err *Error) {
 	return int(r), ErrnoToError(e)
 }
 
-// Seek sets the offset for the next Read or Write on FD to offset, interpreted
+// Seek sets the offset for the next Read or Write on file to offset, interpreted
 // according to whence: 0 means relative to the origin of the file, 1 means
 // relative to the current offset, and 2 means relative to the end.
 // It returns the new offset and an Error, if any.
-func (fd *FD) Seek(offset int64, whence int) (ret int64, err *Error) {
-	r, e := syscall.Seek(fd.fd, offset, int64(whence));
+func (file *File) Seek(offset int64, whence int) (ret int64, err *Error) {
+	r, e := syscall.Seek(file.fd, offset, int64(whence));
 	if e != 0 {
 		return -1, ErrnoToError(e)
 	}
-	if fd.dirinfo != nil && r != 0 {
+	if file.dirinfo != nil && r != 0 {
 		return -1, ErrnoToError(syscall.EISDIR)
 	}
 	return r, nil
@@ -149,20 +148,20 @@ func (fd *FD) Seek(offset int64, whence int) (ret int64, err *Error) {
 
 // WriteString is like Write, but writes the contents of string s rather than
 // an array of bytes.
-func (fd *FD) WriteString(s string) (ret int, err *Error) {
-	if fd == nil {
+func (file *File) WriteString(s string) (ret int, err *Error) {
+	if file == nil {
 		return 0, EINVAL
 	}
-	r, e := syscall.Write(fd.fd, syscall.StringBytePtr(s), int64(len(s)));
+	r, e = syscall.Write(file.fd, syscall.StringBytePtr(s), int64(len(s)));
 	if r < 0 {
 		r = 0
 	}
 	return int(r), ErrnoToError(e)
 }
 
-// Pipe returns a connected pair of FDs; reads from r return bytes written to w.
-// It returns the FDs and an Error, if any.
-func Pipe() (r *FD, w *FD, err *Error) {
+// Pipe returns a connected pair of Files; reads from r return bytes written to w.
+// It returns the files and an Error, if any.
+func Pipe() (r *File, w *File, err *Error) {
 	var p [2]int64;
 
 	// See ../syscall/exec.go for description of lock.
@@ -176,7 +175,7 @@ func Pipe() (r *FD, w *FD, err *Error) {
 	syscall.CloseOnExec(p[1]);
 	syscall.ForkLock.RUnlock();
 
-	return NewFD(p[0], "|0"), NewFD(p[1], "|1"), nil
+	return NewFile(p[0], "|0"), NewFile(p[1], "|1"), nil
 }
 
 // Mkdir creates a new directory with the specified name and permission bits.
@@ -199,15 +198,15 @@ func Stat(name string) (dir *Dir, err *Error) {
 	return dirFromStat(name, new(Dir), stat), nil
 }
 
-// Fstat returns the Dir structure describing the file associated with the FD.
+// Stat returns the Dir structure describing file.
 // It returns the Dir and an error, if any.
-func Fstat(fd *FD) (dir *Dir, err *Error) {
+func (file *File) Stat() (dir *Dir, err *Error) {
 	stat := new(syscall.Stat_t);
-	r, e := syscall.Fstat(fd.fd, stat);
+	r, e := syscall.Fstat(file.fd, stat);
 	if e != 0 {
 		return nil, ErrnoToError(e)
 	}
-	return dirFromStat(fd.name, new(Dir), stat), nil
+	return dirFromStat(file.name, new(Dir), stat), nil
 }
 
 // Lstat returns the Dir structure describing the named file. If the file
@@ -224,26 +223,29 @@ func Lstat(name string) (dir *Dir, err *Error) {
 
 // Readdirnames has a non-portable implemenation so its code is separated into an
 // operating-system-dependent file.
+func readdirnames(file *File, count int) (names []string, err *os.Error)
 
-// Readdirnames reads the contents of the directory associated with fd and
+// Readdirnames reads the contents of the directory associated with file and
 // returns an array of up to count names, in directory order.  Subsequent
-// calls on the same fd will yield further names.
+// calls on the same file will yield further names.
 // A negative count means to read until EOF.
 // It returns the array and an Error, if any.
-func Readdirnames(fd *FD, count int) (names []string, err *Error)
+func (file *File) Readdirnames(count int) (names []string, err *os.Error) {
+	return readdirnames(file, count);
+}
 
-// Readdir reads the contents of the directory associated with fd and
+// Readdir reads the contents of the directory associated with file and
 // returns an array of up to count Dir structures, in directory order.  Subsequent
-// calls on the same fd will yield further Dirs.
+// calls on the same file will yield further Dirs.
 // A negative count means to read until EOF.
 // It returns the array and an Error, if any.
-func Readdir(fd *FD, count int) (dirs []Dir, err *Error) {
-	dirname := fd.name;
+func (file *File) Readdir(count int) (dirs []Dir, err *os.Error) {
+	dirname := file.name;
 	if dirname == "" {
 		dirname = ".";
 	}
 	dirname += "/";
-	names, err1 := Readdirnames(fd, count);
+	names, err1 := file.Readdirnames(count);
 	if err1 != nil {
 		return nil, err1
 	}

src/lib/http/server.go (代表的な影響箇所)

--- a/src/lib/http/server.go
+++ b/src/lib/http/server.go
@@ -40,8 +40,8 @@ type Conn struct {
 	RemoteAddr string;	// network address of remote side
 	Req *Request;	// current HTTP request
 
-	fd io.ReadWriteClose;	// i/o connection
-	buf *bufio.BufReadWrite;	// buffered fd
+	rwc io.ReadWriteClose;	// i/o connection
+	buf *bufio.BufReadWrite;	// buffered rwc
 	handler Handler;	// request handler
 	hijacked bool;	// connection has been hijacked by handler
 
@@ -54,7 +54,7 @@ func newConn(rwc io.ReadWriteClose, raddr string, handler Handler) (c *Conn, err
 	c = new(Conn);
 	c.RemoteAddr = raddr;
 	c.handler = handler;
-	c.fd = rwc;
+	c.rwc = rwc;
 	br := bufio.NewBufRead(rwc);
 	bw := bufio.NewBufWrite(rwc);
 	c.buf = bufio.NewBufReadWrite(br, bw);
@@ -175,9 +175,9 @@ func (c *Conn) close() {
 	if c.buf != nil {
 		c.buf.Flush();
 		c.buf = nil;
 	}
-	if c.fd != nil {
-		c.fd.Close();
-		c.fd = nil;
+	if c.rwc != nil {
+		c.rwc.Close();
+		c.rwc = nil;
 	}
 }
 
@@ -203,10 +203,10 @@ func (c *Conn) Hijack() (fd io.ReadWriteClose, buf *bufio.BufReadWrite, err *os.Error) {
 	if c.hijacked {
 		return nil, nil, ErrHijacked;
 	}
 	c.hijacked = true;
-	fd = c.fd;
+	rwc = c.rwc;
 	buf = c.buf;
-	c.fd = nil;
+	c.rwc = nil;
 	c.buf = nil;
 	return;
 }

コアとなるコードの解説

src/lib/os/file.goの変更点

  1. FD構造体のFile構造体へのリネーム:

    • type FD struct { ... }type File struct { ... } に変更されました。これは、ファイルディスクリプタという低レベルな概念から、より高レベルな「ファイル」という概念への抽象化を意図しています。
    • コメントも「Auxiliary information if the FD describes a directory」から「Auxiliary information if the File describes a directory」に、「FD represents an open file.」から「File represents an open file descriptor.」に変更されています。後者のコメントは、Fileが依然としてファイルディスクリプタを基盤としていることを示唆しつつも、APIの利用者が意識すべきは「ファイル」であるというニュアンスを伝えています。
  2. NewFD関数のNewFile関数へのリネーム:

    • func NewFD(fd int64, name string) *FDfunc NewFile(file int64, name string) *File に変更されました。これは、新しいFileインスタンスを作成するためのコンストラクタ関数です。
  3. 標準ファイルディスクリプタの初期化変更:

    • Stdin, Stdout, Stderrといった標準入出力エラーを表すグローバル変数が、NewFDではなくNewFileを使って初期化されるようになりました。これにより、これらの変数もos.File型として扱われるようになります。
  4. Open関数の戻り値の型変更:

    • func Open(...) (fd *FD, err *Error)func Open(...) (file *File, err *Error) に変更されました。これにより、os.Openが直接*os.Fileインスタンスを返すようになり、開かれたファイルに対してすぐにメソッドを呼び出せるようになります。
  5. Close, Read, Write, Seek, WriteStringメソッドのレシーバ型変更:

    • これらのメソッドのレシーバが (fd *FD) から (file *File) に変更されました。これにより、これらのI/O操作がos.Fileインスタンスのメソッドとして提供されることになります。内部的には、引き続きfile.fd(基盤となる整数値のファイルディスクリプタ)を使用してsyscallを呼び出しています。
  6. Pipe関数の戻り値の型変更:

    • func Pipe() (r *FD, w *FD, err *Error)func Pipe() (r *File, w *File, err *Error) に変更されました。パイプもファイルと同様にos.File型として扱われるようになりました。
  7. Fstat関数のStatメソッドへの変更:

    • グローバル関数であった func Fstat(fd *FD) (dir *Dir, err *Error) が、os.Fileのメソッドである func (file *File) Stat() (dir *Dir, err *Error) に変更されました。これにより、file.Stat()という形でファイルの状態を取得できるようになります。
  8. Readdirnames関数のメソッド化:

    • グローバル関数であった func Readdirnames(fd *FD, count int) (names []string, err *Error) が、os.Fileのメソッドである func (file *File) Readdirnames(count int) (names []string, err *Error) に変更されました。ディレクトリ内のエントリ名を読み取る操作がfile.Readdirnames()として利用可能になります。
  9. Readdir関数のメソッド化:

    • グローバル関数であった func Readdir(fd *FD, count int) (dirs []Dir, err *Error) が、os.Fileのメソッドである func (file *File) Readdir(count int) (dirs []Dir, err *Error) に変更されました。ディレクトリ内のエントリ情報を読み取る操作がfile.Readdir()として利用可能になります。

src/lib/http/server.goの変更点(代表例)

  • Conn構造体内のI/O接続を表すフィールドが、具体的なos.FDではなく、より汎用的なio.ReadWriteCloseインターフェース型として定義されました。
    • fd io.ReadWriteClose;rwc io.ReadWriteClose; に変更されました。
    • これに伴い、newConn関数内での初期化や、closeHijackメソッド内でのアクセスもc.fdからc.rwcに変更されています。
  • この変更は、os.Fileio.ReadWriteCloseインターフェースを実装しているため、HTTPサーバーがファイルディスクリプタの具体的な実装に依存せず、より抽象的なI/Oストリームとして扱えるようにするためのものです。これにより、HTTPサーバーの柔軟性と再利用性が向上します。

これらの変更は、Go言語のAPIが、低レベルなシステムコールへの直接的なマッピングから、より高レベルで直感的なオブジェクト指向的な抽象化へと移行していることを明確に示しています。これにより、開発者はファイルやI/O操作をより自然な形で扱えるようになり、コードの可読性と保守性が大幅に向上します。

関連リンク

参考にした情報源リンク

  • Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
  • Go言語の初期の設計に関するブログ記事やメーリングリストのアーカイブ (具体的なURLは特定が難しいが、Goの歴史を辿る上で有用)
  • Unix系OSにおけるファイルディスクリプタに関する一般的な情報源 (例: Wikipedia, manページ)

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

このコミットは、Go言語の標準ライブラリosパッケージにおけるファイル操作のAPIを大幅に改善するものです。具体的には、ファイルディスクリプタを表す型名をos.FDからos.Fileへと変更し、さらにFstatReaddirnamesReaddirといったファイル関連のグローバル関数をos.File型のメソッドとして再定義しています。これにより、ファイル操作がよりオブジェクト指向的かつ直感的なAPIに統一され、コードの可読性と保守性が向上しています。

コミット

commit 7a706fb3d7642e782f60d3d1d137b3c220643b46
Author: Russ Cox <rsc@golang.org>
Date:   Wed Mar 11 12:51:10 2009 -0700

    Rename os.FD to os.File.
    
    Make Fstat, Readdirnames, and Readdir methods
    on os.File.
    
    R=r
    DELTA=281  (79 added, 3 deleted, 199 changed)
    OCL=25891
    CL=26130

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

https://github.com/golang/go/commit/7a706fb3d7642e7782f60d3d1d137b3c220643b46

元コミット内容

このコミットは、Go言語のosパッケージにおけるファイル操作の基本的な型と関数に焦点を当てたものです。主な変更点は以下の通りです。

  1. os.FDからos.Fileへの型名変更: これまでファイルディスクリプタを抽象化していたos.FD型がos.Fileへと名称変更されました。これは、より直感的で、ファイルという概念を直接的に表す名前にすることで、APIの意図を明確にする狙いがあります。
  2. 関連関数のメソッド化: FstatReaddirnamesReaddirといったファイルに関する操作を行う関数が、os.File型のメソッドとして再定義されました。これにより、これらの操作が特定のFileインスタンスに紐付けられ、よりオブジェクト指向的なスタイルでファイル操作を行えるようになります。

これらの変更は、Go言語の初期段階におけるAPI設計の洗練化の一環であり、より一貫性のある、使いやすい標準ライブラリを目指す方向性を示しています。

変更の背景

この変更の背景には、Go言語のAPI設計哲学と、より直感的で「Goらしい」コードスタイルへの移行があります。初期のGo言語では、C言語の影響を強く受けたAPI設計が見られ、ファイルディスクリプタ(FD)という低レベルな概念が直接的に露出していました。しかし、Go言語が成熟するにつれて、より高レベルで抽象化されたAPIが求められるようになりました。

  • APIの直感性向上: FDという名称は、Unix系のシステムプログラミングに慣れた開発者には馴染み深いものですが、Go言語のユーザーベースが広がるにつれて、より汎用的で理解しやすい「ファイル」という概念を直接的に表すFileという名称が適切であると判断されました。
  • オブジェクト指向的なアプローチの採用: Fstat(ファイルの状態取得)、Readdirnames(ディレクトリ内のエントリ名取得)、Readdir(ディレクトリ内のエントリ情報取得)といった操作は、特定の開かれたファイルやディレクトリに対して行われるものです。これらをグローバル関数として提供するよりも、os.File型のメソッドとして提供することで、操作対象が明確になり、コードの可読性と自己文書化能力が向上します。例えば、Fstat(fd)と書くよりもfile.Stat()と書く方が、fileというオブジェクトに対してStat操作を行っていることが一目瞭然です。
  • 一貫性の確保: Go言語の標準ライブラリ全体で、関連する操作を構造体のメソッドとして提供するパターンが確立されつつありました。この変更は、osパッケージもこのパターンに準拠させ、ライブラリ全体の一貫性を高めることを目的としています。

これらの変更は、Go言語が低レベルなシステムプログラミングの能力を維持しつつも、より現代的で使いやすい高レベルなプログラミング言語としての地位を確立していく過程で不可欠なステップでした。

前提知識の解説

このコミットの変更内容を理解するためには、以下の概念について基本的な知識が必要です。

1. ファイルディスクリプタ (File Descriptor, FD)

  • 定義: Unix系オペレーティングシステムにおいて、プロセスが開いているファイルやI/Oリソース(パイプ、ソケットなど)を識別するために使用される非負の整数値です。
  • 役割: プログラムがファイルシステムやI/Oデバイスとやり取りする際の抽象的なハンドルとして機能します。例えば、open()システムコールはファイルディスクリプタを返し、その後のread()write()システムコールはそのディスクリプタを使って操作対象を指定します。
  • 標準ファイルディスクリプタ:
    • 0: 標準入力 (stdin)
    • 1: 標準出力 (stdout)
    • 2: 標準エラー出力 (stderr)

2. Go言語のosパッケージ

  • 役割: Go言語の標準ライブラリの一部であり、オペレーティングシステム(OS)の機能へのプラットフォーム非依存なインターフェースを提供します。ファイルシステム操作(ファイルの作成、読み書き、削除、ディレクトリ操作など)、プロセス管理、環境変数へのアクセスなどが含まれます。
  • 初期の設計: Go言語の初期段階では、osパッケージはファイルディスクリプタを直接扱うos.FD型を提供していました。これは、低レベルなシステムコールへの薄いラッパーとして機能していました。

3. Go言語のメソッド (Methods)

  • 定義: Go言語において、特定の型(構造体やカスタム型)に関連付けられた関数です。メソッドは、その型のインスタンスに対して操作を実行するために使用されます。
  • 構文: func (receiver Type) MethodName(parameters) (results) { ... }
    • receiver: メソッドが関連付けられる型のインスタンス。慣習的に、ポインタレシーバ(*Type)または値レシーバ(Type)を使用します。
  • 利点:
    • カプセル化: データ(レシーバのフィールド)と、そのデータを操作するロジック(メソッド)を一緒に定義できます。
    • 可読性: object.Method()という形式で呼び出すことで、どのオブジェクトに対して操作が行われているかが明確になります。
    • インターフェースの実装: メソッドを持つことで、その型が特定のインターフェースを満たすことを宣言できます。

4. Go言語のインターフェース (Interfaces)

  • 定義: Go言語において、メソッドのシグネチャの集合を定義する型です。インターフェースは、特定の振る舞いを抽象化するために使用されます。
  • 特徴: Goのインターフェースは「暗黙的」に実装されます。つまり、ある型がインターフェースで定義されたすべてのメソッドを実装していれば、その型はそのインターフェースを満たしているとみなされます。明示的なimplementsキーワードは不要です。
  • io.Reader, io.Writer, io.Closer, io.ReadWriteCloser: ioパッケージで定義されているこれらのインターフェースは、Go言語におけるI/O操作の基本的な抽象化を提供します。
    • io.Reader: Read(p []byte) (n int, err error)メソッドを持つ。
    • io.Writer: Write(p []byte) (n int, err error)メソッドを持つ。
    • io.Closer: Close() errorメソッドを持つ。
    • io.ReadWriteCloser: io.Reader, io.Writer, io.Closerのすべてのメソッドを持つ。

このコミットは、os.FDio.ReadWriteCloserインターフェースを実装していたのと同様に、os.Fileもこれらのインターフェースを引き続き実装することで、既存のI/O関連コードとの互換性を維持しつつ、APIのセマンティクスを向上させています。

技術的詳細

このコミットの技術的な詳細を掘り下げると、Go言語のAPI設計における重要な進化が見て取れます。

1. os.FDからos.Fileへの型名変更

  • 変更点: src/lib/os/file.goにおいて、type FD struct { ... }type File struct { ... }に変更されました。これに伴い、NewFD関数はNewFileに、Stdin, Stdout, Stderrといったグローバル変数もNewFileを使って初期化されるようになりました。
  • 影響:
    • セマンティクスの明確化: FDは「ファイルディスクリプタ」という低レベルな概念を指しますが、Fileは「ファイル」というより高レベルで一般的な概念を指します。この変更により、osパッケージが提供する抽象化レベルがより明確になりました。ユーザーは、OSの内部的なファイルディスクリプタを直接意識することなく、ファイルというオブジェクトとして操作できるようになります。
    • コードの可読性向上: *os.FDという型よりも*os.Fileの方が、それがファイルオブジェクトへのポインタであることが直感的に理解できます。
    • 既存コードへの波及: os.FDを使用していたすべての箇所(exechttpnetlogstrconvtimeパッケージなど)で、型名の変更とそれに伴う変数名の変更(例: fdからfileosfdからfile)が必要となりました。これは、Goの静的型付けの恩恵により、コンパイル時に変更箇所が特定されやすいという側面もあります。

2. Fstat, Readdirnames, Readdirのメソッド化

  • 変更点:
    • func Fstat(fd *FD) (dir *Dir, err *Error)func (file *File) Stat() (dir *Dir, err *Error)に変更されました。
    • func Readdirnames(fd *FD, count int) (names []string, err *Error)func (file *File) Readdirnames(count int) (names []string, err *Error)に変更されました。
    • func Readdir(fd *FD, count int) (dirs []Dir, err *Error)func (file *File) Readdir(count int) (dirs []Dir, err *Error)に変更されました。
  • 影響:
    • オブジェクト指向的なAPI: これらの関数は、特定の開かれたファイルやディレクトリの状態を取得したり、内容を読み取ったりする操作です。これらをos.Fileのメソッドとすることで、操作対象がFileインスタンス自身であることが明確になり、file.Stat()のように自然な呼び出しが可能になります。これは、Go言語が推奨する「レシーバを持つ関数」のパターンに合致します。
    • カプセル化の強化: File構造体内部のfd(ファイルディスクリプタの整数値)やdirinfo(ディレクトリ情報)といったフィールドは、外部から直接アクセスされるべきではありません。メソッドを通じてこれらの内部状態を操作することで、APIの利用者は実装の詳細を意識することなく、高レベルなファイル操作に集中できます。
    • インターフェースとの整合性: os.Fileio.Readerio.Writerio.Closerといった基本的なI/Oインターフェースを実装しています。今回の変更により、StatReaddirnamesといったファイルシステム固有の操作もFile型に集約され、ファイル関連の機能がより一貫した形で提供されるようになりました。
    • os.Openの戻り値: os.Open関数も、*os.FDではなく*os.Fileを返すように変更されました。これにより、ファイルを開いた直後にそのFileインスタンスに対して直接メソッドを呼び出すことができるようになり、コードの簡潔性が向上します。

3. その他の影響箇所

  • src/lib/exec.go: プロセス実行時の標準入出力エラーを扱うCmd構造体のフィールドが*os.FDから*os.Fileに変更され、関連するヘルパー関数modeToFDsmodeToFilesにリネームされました。
  • src/lib/http/server.go: HTTPコネクションを表すConn構造体内のI/O接続を表すフィールドがfd io.ReadWriteCloseからrwc io.ReadWriteCloseに変更され、それに伴いc.fdへのアクセスがc.rwcに変更されました。これは、os.Fileio.ReadWriteCloseインターフェースを満たすため、より汎用的なインターフェース型を使用することで、HTTPサーバーが特定のファイルディスクリプタ実装に依存しないようにする変更です。
  • src/lib/net/fd.go: ネットワークファイルディスクリプタを扱うnetFD構造体内のosfd *os.FDフィールドがfile *os.Fileに変更されました。これは、ネットワークI/OもファイルI/Oと同様にos.Fileの抽象化の下で扱われることを示しています。
  • src/lib/log_test.go, src/lib/strconv/fp_test.go, src/lib/time/zoneinfo.go: これらのテストファイルやユーティリティファイルでも、os.Openの戻り値やos.Pipeの戻り値が*os.File型として扱われるように修正されています。

これらの変更は、Go言語の標準ライブラリが、より抽象的で、使いやすく、そして「Goらしい」APIデザインへと進化していく過程における重要なマイルストーンを示しています。低レベルなOSの概念を適切に抽象化し、開発者がより高レベルなアプリケーションロジックに集中できるようにするための努力が反映されています。

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

このコミットのコアとなる変更は、src/lib/os/file.goに集中しています。

src/lib/os/file.go

--- a/src/lib/os/file.go
+++ b/src/lib/os/file.go
@@ -11,45 +11,44 @@ import (
 	"syscall";
 )
 
-// Auxiliary information if the FD describes a directory
+// Auxiliary information if the File describes a directory
 type dirInfo struct {	// TODO(r): 6g bug means this can't be private
 	buf	[]byte;	// buffer for directory I/O
 	nbuf	int64;	// length of buf; return value from Getdirentries
 	bufp	int64;	// location of next record in buf.
 }
 
-// FD represents an open file.
-// TODO(r): is FD the right name? Would File be better?
-type FD struct {
+// File represents an open file descriptor.
+type File struct {
 	fd int64;
 	name	string;
 	dirinfo	*dirInfo;	// nil unless directory being read
 }
 
 // Fd returns the integer Unix file descriptor referencing the open file.
-func (fd *FD) Fd() int64 {
-	return fd.fd
+func (file *File) Fd() int64 {
+	return file.fd
 }
 
 // Name returns the name of the file as presented to Open.
-func (fd *FD) Name() string {
-	return fd.name
+func (file *File) Name() string {
+	return file.name
 }
 
-// NewFD returns a new FD with the given file descriptor and name.
-func NewFD(fd int64, name string) *FD {
-	if fd < 0 {
+// NewFile returns a new File with the given file descriptor and name.
+func NewFile(file int64, name string) *File {
+	if file < 0 {
 		return nil
 	}
-	return &FD{fd, name, nil}
+	return &File{file, name, nil}
 }
 
-// Stdin, Stdout, and Stderr are open FDs pointing to the standard input,
+// Stdin, Stdout, and Stderr are open Files pointing to the standard input,
 // standard output, and standard error file descriptors.
 var (
-	Stdin = NewFD(0, "/dev/stdin");
-	Stdout = NewFD(1, "/dev/stdout");
-	Stderr = NewFD(2, "/dev/stderr");
+	Stdin  = NewFile(0, "/dev/stdin");
+	Stdout = NewFile(1, "/dev/stdout");
+	Stderr = NewFile(2, "/dev/stderr");
 )
 
 // Flags to Open wrapping those of the underlying system. Not all flags
@@ -69,9 +68,9 @@ const (
 )
 
 // Open opens the named file with specified flag (O_RDONLY etc.) and perm, (0666 etc.)
-// if applicable.  If successful, methods on the returned FD can be used for I/O.\n// It returns the FD and an Error, if any.\n-func Open(name string, flag int, perm int) (fd *FD, err *Error) {
+// if applicable.  If successful, methods on the returned File can be used for I/O.
+// It returns the File and an Error, if any.
+func Open(name string, flag int, perm int) (file *File, err *Error) {
 	r, e := syscall.Open(name, int64(flag | syscall.O_CLOEXEC), int64(perm));
 	if e != 0 {
 		return nil, ErrnoToError(e);
@@ -83,31 +82,31 @@ func Open(name string, flag int, perm int) (fd *FD, err *Error) {
 		syscall.CloseOnExec(r);
 	}
 
-	return NewFD(r, name), ErrnoToError(e)
+	return NewFile(r, name), ErrnoToError(e)
 }
 
-// Close closes the FD, rendering it unusable for I/O.
+// Close closes the File, rendering it unusable for I/O.
 // It returns an Error, if any.
-func (fd *FD) Close() *Error {
-	if fd == nil {
+func (file *File) Close() *Error {
+	if file == nil {
 		return EINVAL
 	}
-	r, e := syscall.Close(fd.fd);
-	fd.fd = -1;  // so it can't be closed again
+	r, e := syscall.Close(file.fd);
+	file.fd = -1;  // so it can't be closed again
 	return ErrnoToError(e)
 }
 
-// Read reads up to len(b) bytes from the FD.
+// Read reads up to len(b) bytes from the File.
 // It returns the number of bytes read and an Error, if any.
 // EOF is signaled by a zero count with a nil Error.
 // TODO(r): Add Pread, Pwrite (maybe ReadAt, WriteAt).
-func (fd *FD) Read(b []byte) (ret int, err *Error) {
-	if fd == nil {
+func (file *File) Read(b []byte) (ret int, err *Error) {
+	if file == nil {
 		return 0, EINVAL
 	}
 	var r, e int64;
 	if len(b) > 0 {  // because we access b[0]
-		r, e = syscall.Read(fd.fd, &b[0], int64(len(b)));
+		r, e = syscall.Read(file.fd, &b[0], int64(len(b)));
 		if r < 0 {
 			r = 0
 		}
@@ -115,16 +114,16 @@ func (fd *FD) Read(b []byte) (ret int, err *Error) {
 	return int(r), ErrnoToError(e)
 }
 
-// Write writes len(b) bytes to the FD.
+// Write writes len(b) bytes to the File.
 // It returns the number of bytes written and an Error, if any.
 // If the byte count differs from len(b), it usually implies an error occurred.
-func (fd *FD) Write(b []byte) (ret int, err *Error) {
-	if fd == nil {
+func (file *File) Write(b []byte) (ret int, err *Error) {
+	if file == nil {
 		return 0, EINVAL
 	}
 	var r, e int64;
 	if len(b) > 0 {  // because we access b[0]
-		r, e = syscall.Write(fd.fd, &b[0], int64(len(b)));
+		r, e = syscall.Write(file.fd, &b[0], int64(len(b)));
 		if r < 0 {
 			r = 0
 		}
@@ -132,16 +131,16 @@ func (fd *FD) Write(b []byte) (ret int, err *Error) {
 	return int(r), ErrnoToError(e)
 }
 
-// Seek sets the offset for the next Read or Write on FD to offset, interpreted
+// Seek sets the offset for the next Read or Write on file to offset, interpreted
 // according to whence: 0 means relative to the origin of the file, 1 means
 // relative to the current offset, and 2 means relative to the end.
 // It returns the new offset and an Error, if any.
-func (fd *FD) Seek(offset int64, whence int) (ret int64, err *Error) {
-	r, e := syscall.Seek(fd.fd, offset, int64(whence));
+func (file *File) Seek(offset int64, whence int) (ret int64, err *Error) {
+	r, e := syscall.Seek(file.fd, offset, int64(whence));
 	if e != 0 {
 		return -1, ErrnoToError(e)
 	}
-	if fd.dirinfo != nil && r != 0 {
+	if file.dirinfo != nil && r != 0 {
 		return -1, ErrnoToError(syscall.EISDIR)
 	}
 	return r, nil
@@ -149,20 +148,20 @@ func (fd *FD) Seek(offset int64, whence int) (ret int64, err *Error) {
 
 // WriteString is like Write, but writes the contents of string s rather than
 // an array of bytes.
-func (fd *FD) WriteString(s string) (ret int, err *Error) {
-	if fd == nil {
+func (file *File) WriteString(s string) (ret int, err *Error) {
+	if file == nil {
 		return 0, EINVAL
 	}
-	r, e := syscall.Write(fd.fd, syscall.StringBytePtr(s), int64(len(s)));
+	r, e = syscall.Write(file.fd, syscall.StringBytePtr(s), int64(len(s)));
 	if r < 0 {
 		r = 0
 	}
 	return int(r), ErrnoToError(e)
 }
 
-// Pipe returns a connected pair of FDs; reads from r return bytes written to w.
-// It returns the FDs and an Error, if any.
-func Pipe() (r *FD, w *FD, err *Error) {
+// Pipe returns a connected pair of Files; reads from r return bytes written to w.
+// It returns the files and an Error, if any.
+func Pipe() (r *File, w *File, err *Error) {
 	var p [2]int64;
 
 	// See ../syscall/exec.go for description of lock.
@@ -176,7 +175,7 @@ func Pipe() (r *FD, w *FD, err *Error) {
 	syscall.CloseOnExec(p[1]);
 	syscall.ForkLock.RUnlock();
 
-	return NewFD(p[0], "|0"), NewFD(p[1], "|1"), nil
+	return NewFile(p[0], "|0"), NewFile(p[1], "|1"), nil
 }
 
 // Mkdir creates a new directory with the specified name and permission bits.
@@ -199,15 +198,15 @@ func Stat(name string) (dir *Dir, err *Error) {
 	return dirFromStat(name, new(Dir), stat), nil
 }
 
-// Fstat returns the Dir structure describing the file associated with the FD.
+// Stat returns the Dir structure describing file.
 // It returns the Dir and an error, if any.
-func Fstat(fd *FD) (dir *Dir, err *Error) {
+func (file *File) Stat() (dir *Dir, err *Error) {
 	stat := new(syscall.Stat_t);
-	r, e := syscall.Fstat(fd.fd, stat);
+	r, e := syscall.Fstat(file.fd, stat);
 	if e != 0 {
 		return nil, ErrnoToError(e)
 	}
-	return dirFromStat(fd.name, new(Dir), stat), nil
+	return dirFromStat(file.name, new(Dir), stat), nil
 }
 
 // Lstat returns the Dir structure describing the named file. If the file
@@ -224,26 +223,29 @@ func Lstat(name string) (dir *Dir, err *Error) {
 
 // Readdirnames has a non-portable implemenation so its code is separated into an
 // operating-system-dependent file.
+func readdirnames(file *File, count int) (names []string, err *os.Error)
 
-// Readdirnames reads the contents of the directory associated with fd and
+// Readdirnames reads the contents of the directory associated with file and
 // returns an array of up to count names, in directory order.  Subsequent
-// calls on the same fd will yield further names.
+// calls on the same file will yield further names.
 // A negative count means to read until EOF.
 // It returns the array and an Error, if any.
-func Readdirnames(fd *FD, count int) (names []string, err *os.Error)
+func (file *File) Readdirnames(count int) (names []string, err *os.Error) {
+	return readdirnames(file, count);
+}
 
-// Readdir reads the contents of the directory associated with fd and
+// Readdir reads the contents of the directory associated with file and
 // returns an array of up to count Dir structures, in directory order.  Subsequent
-// calls on the same fd will yield further Dirs.
+// calls on the same file will yield further Dirs.
 // A negative count means to read until EOF.
 // It returns the array and an Error, if any.
-func Readdir(fd *FD, count int) (dirs []Dir, err *Error) {
-	dirname := fd.name;
+func (file *File) Readdir(count int) (dirs []Dir, err *os.Error) {
+	dirname := file.name;
 	if dirname == "" {
 		dirname = ".";
 	}
 	dirname += "/";
-	names, err1 := Readdirnames(fd, count);
+	names, err1 := file.Readdirnames(count);
 	if err1 != nil {
 		return nil, err1
 	}

src/lib/http/server.go (代表的な影響箇所)

--- a/src/lib/http/server.go
+++ b/src/lib/http/server.go
@@ -40,8 +40,8 @@ type Conn struct {
 	RemoteAddr string;	// network address of remote side
 	Req *Request;	// current HTTP request
 
-	fd io.ReadWriteClose;	// i/o connection
-	buf *bufio.BufReadWrite;	// buffered fd
+	rwc io.ReadWriteClose;	// i/o connection
+	buf *bufio.BufReadWrite;	// buffered rwc
 	handler Handler;	// request handler
 	hijacked bool;	// connection has been hijacked by handler
 
@@ -54,7 +54,7 @@ func newConn(rwc io.ReadWriteClose, raddr string, handler Handler) (c *Conn, err
 	c = new(Conn);
 	c.RemoteAddr = raddr;
 	c.handler = handler;
-	c.fd = rwc;
+	c.rwc = rwc;
 	br := bufio.NewBufRead(rwc);
 	bw := bufio.NewBufWrite(rwc);
 	c.buf = bufio.NewBufReadWrite(br, bw);
@@ -175,9 +175,9 @@ func (c *Conn) close() {
 	if c.buf != nil {
 		c.buf.Flush();
 		c.buf = nil;
 	}
-	if c.fd != nil {
-		c.fd.Close();
-		c.fd = nil;
+	if c.rwc != nil {
+		c.rwc.Close();
+		c.rwc = nil;
 	}
 }
 
@@ -203,10 +203,10 @@ func (c *Conn) Hijack() (fd io.ReadWriteClose, buf *bufio.BufReadWrite, err *os.Error) {
 	if c.hijacked {
 		return nil, nil, ErrHijacked;
 	}
 	c.hijacked = true;
-	fd = c.fd;
+	rwc = c.rwc;
 	buf = c.buf;
-	c.fd = nil;
+	c.rwc = nil;
 	c.buf = nil;
 	return;
 }

コアとなるコードの解説

src/lib/os/file.goの変更点

  1. FD構造体のFile構造体へのリネーム:

    • type FD struct { ... }type File struct { ... } に変更されました。これは、ファイルディスクリプタという低レベルな概念から、より高レベルな「ファイル」という概念への抽象化を意図しています。
    • コメントも「Auxiliary information if the FD describes a directory」から「Auxiliary information if the File describes a directory」に、「FD represents an open file.」から「File represents an open file descriptor.」に変更されています。後者のコメントは、Fileが依然としてファイルディスクリプタを基盤としていることを示唆しつつも、APIの利用者が意識すべきは「ファイル」であるというニュアンスを伝えています。
  2. NewFD関数のNewFile関数へのリネーム:

    • func NewFD(fd int64, name string) *FDfunc NewFile(file int64, name string) *File に変更されました。これは、新しいFileインスタンスを作成するためのコンストラクタ関数です。
  3. 標準ファイルディスクリプタの初期化変更:

    • Stdin, Stdout, Stderrといった標準入出力エラーを表すグローバル変数が、NewFDではなくNewFileを使って初期化されるようになりました。これにより、これらの変数もos.File型として扱われるようになります。
  4. Open関数の戻り値の型変更:

    • func Open(...) (fd *FD, err *Error)func Open(...) (file *File, err *Error) に変更されました。これにより、os.Openが直接*os.Fileインスタンスを返すようになり、開かれたファイルに対してすぐにメソッドを呼び出せるようになります。
  5. Close, Read, Write, Seek, WriteStringメソッドのレシーバ型変更:

    • これらのメソッドのレシーバが (fd *FD) から (file *File) に変更されました。これにより、これらのI/O操作がos.Fileインスタンスのメソッドとして提供されることになります。内部的には、引き続きfile.fd(基盤となる整数値のファイルディスクリプタ)を使用してsyscallを呼び出しています。
  6. Pipe関数の戻り値の型変更:

    • func Pipe() (r *FD, w *FD, err *Error)func Pipe() (r *File, w *File, err *Error) に変更されました。パイプもファイルと同様にos.File型として扱われるようになりました。
  7. Fstat関数のStatメソッドへの変更:

    • グローバル関数であった func Fstat(fd *FD) (dir *Dir, err *Error) が、os.Fileのメソッドである func (file *File) Stat() (dir *Dir, err *Error) に変更されました。これにより、file.Stat()という形でファイルの状態を取得できるようになります。
  8. Readdirnames関数のメソッド化:

    • グローバル関数であった func Readdirnames(fd *FD, count int) (names []string, err *Error) が、os.Fileのメソッドである func (file *File) Readdirnames(count int) (names []string, err *Error) に変更されました。ディレクトリ内のエントリ名を読み取る操作がfile.Readdirnames()として利用可能になります。
  9. Readdir関数のメソッド化:

    • グローバル関数であった func Readdir(fd *FD, count int) (dirs []Dir, err *Error) が、os.Fileのメソッドである func (file *File) Readdir(count int) (dirs []Dir, err *Error) に変更されました。ディレクトリ内のエントリ情報を読み取る操作がfile.Readdir()として利用可能になります。

src/lib/http/server.goの変更点(代表例)

  • Conn構造体内のI/O接続を表すフィールドが、具体的なos.FDではなく、より汎用的なio.ReadWriteCloseインターフェース型として定義されました。
    • fd io.ReadWriteClose;rwc io.ReadWriteClose; に変更されました。
    • これに伴い、newConn関数内での初期化や、closeHijackメソッド内でのアクセスもc.fdからc.rwcに変更されています。
  • この変更は、os.Fileio.ReadWriteCloseインターフェースを実装しているため、HTTPサーバーがファイルディスクリプタの具体的な実装に依存せず、より抽象的なI/Oストリームとして扱えるようにするためのものです。これにより、HTTPサーバーの柔軟性と再利用性が向上します。

これらの変更は、Go言語のAPIが、低レベルなシステムコールへの直接的なマッピングから、より高レベルで直感的なオブジェクト指向的な抽象化へと移行していることを明確に示しています。これにより、開発者はファイルやI/O操作をより自然な形で扱えるようになり、コードの可読性と保守性が大幅に向上します。

関連リンク

参考にした情報源リンク

  • Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
  • Go言語の初期の設計に関するブログ記事やメーリングリストのアーカイブ (具体的なURLは特定が難しいが、Goの歴史を辿る上で有用)
  • Unix系OSにおけるファイルディスクリプタに関する一般的な情報源 (例: Wikipedia, manページ)