[インデックス 1808] ファイルの概要
このコミットは、Go言語の標準ライブラリos
パッケージにおけるファイル操作のAPIを大幅に改善するものです。具体的には、ファイルディスクリプタを表す型名をos.FD
からos.File
へと変更し、さらにFstat
、Readdirnames
、Readdir
といったファイル関連のグローバル関数を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
パッケージにおけるファイル操作の基本的な型と関数に焦点を当てたものです。主な変更点は以下の通りです。
os.FD
からos.File
への型名変更: これまでファイルディスクリプタを抽象化していたos.FD
型がos.File
へと名称変更されました。これは、より直感的で、ファイルという概念を直接的に表す名前にすることで、APIの意図を明確にする狙いがあります。- 関連関数のメソッド化:
Fstat
、Readdirnames
、Readdir
といったファイルに関する操作を行う関数が、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.FD
がio.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
を使用していたすべての箇所(exec
、http
、net
、log
、strconv
、time
パッケージなど)で、型名の変更とそれに伴う変数名の変更(例:fd
からfile
、osfd
から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.File
はio.Reader
、io.Writer
、io.Closer
といった基本的なI/Oインターフェースを実装しています。今回の変更により、Stat
やReaddirnames
といったファイルシステム固有の操作もFile
型に集約され、ファイル関連の機能がより一貫した形で提供されるようになりました。 os.Open
の戻り値:os.Open
関数も、*os.FD
ではなく*os.File
を返すように変更されました。これにより、ファイルを開いた直後にそのFile
インスタンスに対して直接メソッドを呼び出すことができるようになり、コードの簡潔性が向上します。
- オブジェクト指向的なAPI: これらの関数は、特定の開かれたファイルやディレクトリの状態を取得したり、内容を読み取ったりする操作です。これらを
3. その他の影響箇所
src/lib/exec.go
: プロセス実行時の標準入出力エラーを扱うCmd
構造体のフィールドが*os.FD
から*os.File
に変更され、関連するヘルパー関数modeToFDs
もmodeToFiles
にリネームされました。src/lib/http/server.go
: HTTPコネクションを表すConn
構造体内のI/O接続を表すフィールドがfd io.ReadWriteClose
からrwc io.ReadWriteClose
に変更され、それに伴いc.fd
へのアクセスがc.rwc
に変更されました。これは、os.File
がio.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
の変更点
-
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の利用者が意識すべきは「ファイル」であるというニュアンスを伝えています。
-
NewFD
関数のNewFile
関数へのリネーム:func NewFD(fd int64, name string) *FD
がfunc NewFile(file int64, name string) *File
に変更されました。これは、新しいFile
インスタンスを作成するためのコンストラクタ関数です。
-
標準ファイルディスクリプタの初期化変更:
Stdin
,Stdout
,Stderr
といった標準入出力エラーを表すグローバル変数が、NewFD
ではなくNewFile
を使って初期化されるようになりました。これにより、これらの変数もos.File
型として扱われるようになります。
-
Open
関数の戻り値の型変更:func Open(...) (fd *FD, err *Error)
がfunc Open(...) (file *File, err *Error)
に変更されました。これにより、os.Open
が直接*os.File
インスタンスを返すようになり、開かれたファイルに対してすぐにメソッドを呼び出せるようになります。
-
Close
,Read
,Write
,Seek
,WriteString
メソッドのレシーバ型変更:- これらのメソッドのレシーバが
(fd *FD)
から(file *File)
に変更されました。これにより、これらのI/O操作がos.File
インスタンスのメソッドとして提供されることになります。内部的には、引き続きfile.fd
(基盤となる整数値のファイルディスクリプタ)を使用してsyscall
を呼び出しています。
- これらのメソッドのレシーバが
-
Pipe
関数の戻り値の型変更:func Pipe() (r *FD, w *FD, err *Error)
がfunc Pipe() (r *File, w *File, err *Error)
に変更されました。パイプもファイルと同様にos.File
型として扱われるようになりました。
-
Fstat
関数のStat
メソッドへの変更:- グローバル関数であった
func Fstat(fd *FD) (dir *Dir, err *Error)
が、os.File
のメソッドであるfunc (file *File) Stat() (dir *Dir, err *Error)
に変更されました。これにより、file.Stat()
という形でファイルの状態を取得できるようになります。
- グローバル関数であった
-
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()
として利用可能になります。
- グローバル関数であった
-
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
関数内での初期化や、close
、Hijack
メソッド内でのアクセスもc.fd
からc.rwc
に変更されています。
- この変更は、
os.File
がio.ReadWriteClose
インターフェースを実装しているため、HTTPサーバーがファイルディスクリプタの具体的な実装に依存せず、より抽象的なI/Oストリームとして扱えるようにするためのものです。これにより、HTTPサーバーの柔軟性と再利用性が向上します。
これらの変更は、Go言語のAPIが、低レベルなシステムコールへの直接的なマッピングから、より高レベルで直感的なオブジェクト指向的な抽象化へと移行していることを明確に示しています。これにより、開発者はファイルやI/O操作をより自然な形で扱えるようになり、コードの可読性と保守性が大幅に向上します。
関連リンク
- Go言語の
os
パッケージ公式ドキュメント (現在のバージョン): https://pkg.go.dev/os - Go言語の
io
パッケージ公式ドキュメント: https://pkg.go.dev/io - Go言語の設計哲学に関する議論 (Goの進化の背景を理解する上で役立つ可能性があります): https://go.dev/doc/faq#principles
参考にした情報源リンク
- Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
- Go言語の初期の設計に関するブログ記事やメーリングリストのアーカイブ (具体的なURLは特定が難しいが、Goの歴史を辿る上で有用)
- Unix系OSにおけるファイルディスクリプタに関する一般的な情報源 (例: Wikipedia, manページ)
[インデックス 1808] ファイルの概要
このコミットは、Go言語の標準ライブラリos
パッケージにおけるファイル操作のAPIを大幅に改善するものです。具体的には、ファイルディスクリプタを表す型名をos.FD
からos.File
へと変更し、さらにFstat
、Readdirnames
、Readdir
といったファイル関連のグローバル関数を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
パッケージにおけるファイル操作の基本的な型と関数に焦点を当てたものです。主な変更点は以下の通りです。
os.FD
からos.File
への型名変更: これまでファイルディスクリプタを抽象化していたos.FD
型がos.File
へと名称変更されました。これは、より直感的で、ファイルという概念を直接的に表す名前にすることで、APIの意図を明確にする狙いがあります。- 関連関数のメソッド化:
Fstat
、Readdirnames
、Readdir
といったファイルに関する操作を行う関数が、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.FD
がio.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
を使用していたすべての箇所(exec
、http
、net
、log
、strconv
、time
パッケージなど)で、型名の変更とそれに伴う変数名の変更(例:fd
からfile
、osfd
から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.File
はio.Reader
、io.Writer
、io.Closer
といった基本的なI/Oインターフェースを実装しています。今回の変更により、Stat
やReaddirnames
といったファイルシステム固有の操作もFile
型に集約され、ファイル関連の機能がより一貫した形で提供されるようになりました。 os.Open
の戻り値:os.Open
関数も、*os.FD
ではなく*os.File
を返すように変更されました。これにより、ファイルを開いた直後にそのFile
インスタンスに対して直接メソッドを呼び出すことができるようになり、コードの簡潔性が向上します。
- オブジェクト指向的なAPI: これらの関数は、特定の開かれたファイルやディレクトリの状態を取得したり、内容を読み取ったりする操作です。これらを
3. その他の影響箇所
src/lib/exec.go
: プロセス実行時の標準入出力エラーを扱うCmd
構造体のフィールドが*os.FD
から*os.File
に変更され、関連するヘルパー関数modeToFDs
もmodeToFiles
にリネームされました。src/lib/http/server.go
: HTTPコネクションを表すConn
構造体内のI/O接続を表すフィールドがfd io.ReadWriteClose
からrwc io.ReadWriteClose
に変更され、それに伴いc.fd
へのアクセスがc.rwc
に変更されました。これは、os.File
がio.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
の変更点
-
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の利用者が意識すべきは「ファイル」であるというニュアンスを伝えています。
-
NewFD
関数のNewFile
関数へのリネーム:func NewFD(fd int64, name string) *FD
がfunc NewFile(file int64, name string) *File
に変更されました。これは、新しいFile
インスタンスを作成するためのコンストラクタ関数です。
-
標準ファイルディスクリプタの初期化変更:
Stdin
,Stdout
,Stderr
といった標準入出力エラーを表すグローバル変数が、NewFD
ではなくNewFile
を使って初期化されるようになりました。これにより、これらの変数もos.File
型として扱われるようになります。
-
Open
関数の戻り値の型変更:func Open(...) (fd *FD, err *Error)
がfunc Open(...) (file *File, err *Error)
に変更されました。これにより、os.Open
が直接*os.File
インスタンスを返すようになり、開かれたファイルに対してすぐにメソッドを呼び出せるようになります。
-
Close
,Read
,Write
,Seek
,WriteString
メソッドのレシーバ型変更:- これらのメソッドのレシーバが
(fd *FD)
から(file *File)
に変更されました。これにより、これらのI/O操作がos.File
インスタンスのメソッドとして提供されることになります。内部的には、引き続きfile.fd
(基盤となる整数値のファイルディスクリプタ)を使用してsyscall
を呼び出しています。
- これらのメソッドのレシーバが
-
Pipe
関数の戻り値の型変更:func Pipe() (r *FD, w *FD, err *Error)
がfunc Pipe() (r *File, w *File, err *Error)
に変更されました。パイプもファイルと同様にos.File
型として扱われるようになりました。
-
Fstat
関数のStat
メソッドへの変更:- グローバル関数であった
func Fstat(fd *FD) (dir *Dir, err *Error)
が、os.File
のメソッドであるfunc (file *File) Stat() (dir *Dir, err *Error)
に変更されました。これにより、file.Stat()
という形でファイルの状態を取得できるようになります。
- グローバル関数であった
-
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()
として利用可能になります。
- グローバル関数であった
-
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
関数内での初期化や、close
、Hijack
メソッド内でのアクセスもc.fd
からc.rwc
に変更されています。
- この変更は、
os.File
がio.ReadWriteClose
インターフェースを実装しているため、HTTPサーバーがファイルディスクリプタの具体的な実装に依存せず、より抽象的なI/Oストリームとして扱えるようにするためのものです。これにより、HTTPサーバーの柔軟性と再利用性が向上します。
これらの変更は、Go言語のAPIが、低レベルなシステムコールへの直接的なマッピングから、より高レベルで直感的なオブジェクト指向的な抽象化へと移行していることを明確に示しています。これにより、開発者はファイルやI/O操作をより自然な形で扱えるようになり、コードの可読性と保守性が大幅に向上します。
関連リンク
- Go言語の
os
パッケージ公式ドキュメント (現在のバージョン): https://pkg.go.dev/os - Go言語の
io
パッケージ公式ドキュメント: https://pkg.go.dev/io - Go言語の設計哲学に関する議論 (Goの進化の背景を理解する上で役立つ可能性があります): https://go.dev/doc/faq#principles
参考にした情報源リンク
- Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
- Go言語の初期の設計に関するブログ記事やメーリングリストのアーカイブ (具体的なURLは特定が難しいが、Goの歴史を辿る上で有用)
- Unix系OSにおけるファイルディスクリプタに関する一般的な情報源 (例: Wikipedia, manページ)