[インデックス 10185] ファイルの概要
このコミットは、Go言語の標準ライブラリos
パッケージにおけるエラーハンドリングのメカニズムを、よりGoらしい慣用的な方法へと移行させる重要な変更を加えています。具体的には、カスタムエラー型os.Error
の使用を廃止し、Goの組み込みインターフェースであるerror
と、io
パッケージで定義されているio.EOF
定数に置き換えています。これにより、エラー処理の一貫性と相互運用性が向上しています。
コミット
commit 08a073a180aacd3b17a999687ced6b54a313d842
Author: Russ Cox <rsc@golang.org>
Date: Tue Nov 1 21:49:08 2011 -0400
os: use error, io.EOF
R=r
CC=golang-dev
https://golang.org/cl/5298073
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/08a073a180aacd3b17a999687ced6b54a313d842
元コミット内容
このコミットの元の内容は、「os: use error, io.EOF」と簡潔に述べられています。これは、os
パッケージ内で独自に定義されていたエラー型os.Error
と、ファイル終端を示すos.EOF
を、それぞれGo言語の組み込みerror
インターフェースと、標準ライブラリio
パッケージのio.EOF
に置き換えることを意味しています。
変更の背景
Go言語の初期段階では、エラーハンドリングのパターンがまだ確立されていませんでした。os
パッケージでは、独自のError
インターフェースとNewError
関数、そしてEOF
定数が定義され、使用されていました。しかし、Go言語の設計思想として、エラーはシンプルなインターフェース(error
インターフェース)として扱うことが推奨され、標準ライブラリ全体で一貫したエラー処理を行う必要がありました。
このコミットが行われた2011年11月は、Go言語がまだ活発に開発されており、APIの安定化と標準化が進められていた時期にあたります。os.Error
のようなパッケージ固有のエラー型は、異なるパッケージ間でエラーをやり取りする際に不便であり、また、Goの組み込みerror
インターフェースの柔軟性を十分に活用できていませんでした。
io.EOF
への移行も同様の理由です。ファイルやストリームの終端を示すエラーは、I/O操作全般に共通する概念であり、os
パッケージだけでなく、io
パッケージなど他のI/O関連パッケージでも使用されるべきです。そのため、io.EOF
として一元化することで、より汎用的で再利用可能なエラー定数として扱えるようになります。
この変更は、Go言語のエラーハンドリングが、現在私たちが知る「エラーは戻り値として扱い、error
インターフェースを実装する」というシンプルかつ強力なパターンへと収束していく過程の一部でした。
前提知識の解説
Go言語のエラーハンドリングの基本
Go言語では、例外処理のメカニズム(try-catchなど)は採用されていません。代わりに、関数はエラーを通常の戻り値として返します。慣例として、エラーは関数の最後の戻り値として返され、その型は組み込みのerror
インターフェースです。
error
インターフェースは非常にシンプルで、Error() string
というメソッドを一つだけ持ちます。このメソッドは、エラーの文字列表現を返します。
type error interface {
Error() string
}
関数がエラーを返さない場合は、エラー戻り値としてnil
を返します。呼び出し元は、返されたエラーがnil
かどうかをチェックすることで、処理が成功したか失敗したかを判断します。
result, err := someFunction()
if err != nil {
// エラー処理
}
// 成功時の処理
io.EOF
io.EOF
は、Goの標準ライブラリio
パッケージで定義されているエラー変数です。これは、入力ストリームの終端に達したことを示すために使用されます。例えば、ファイルからデータを読み込む際に、読み込むべきデータがもうない場合にio.EOF
が返されます。
package io
import "errors"
// EOF is the error returned by Read when no more input is available.
// Functions should return EOF only to signal a graceful end of input.
// If the EOF occurs unexpectedly in a structured data stream,
// the appropriate error is either ErrUnexpectedEOF or some other error
// giving more detail.
var EOF = errors.New("EOF")
このコミット以前は、os
パッケージ内に独自のEOF
定数(os.EOF
)が存在していましたが、I/O操作全般で共通のエラー定数を使用するため、io.EOF
に統一されました。
PathError
とLinkError
Goのos
パッケージでは、ファイルシステム操作に関連するエラーをより詳細に表現するために、PathError
とLinkError
という構造体が定義されています。
PathError
: ファイルパスに関連する操作(例:Open
,Read
,Write
,Mkdir
など)で発生したエラーをラップします。Op
(操作名)、Path
(関連するパス)、そして元のerror
を含みます。LinkError
: リンク操作(例:Link
,Symlink
,Rename
など)で発生したエラーをラップします。Op
(操作名)、Old
(古いパス)、New
(新しいパス)、そして元のerror
を含みます。
このコミットでは、これらの構造体内のError
フィールドの型が、カスタムのos.Error
から組み込みのerror
インターフェースに変更されています。
技術的詳細
このコミットの主要な技術的変更点は、os
パッケージ内のエラー処理の統一です。
-
os.Error
インターフェースの削除:src/pkg/os/error.go
から、Error
インターフェース、errorString
型、およびNewError
関数が完全に削除されました。これにより、os
パッケージは独自のエラー型を持つことをやめ、Goの組み込みerror
インターフェースに完全に依存するようになりました。変更前:
type Error interface { String() string } type errorString string func (e errorString) String() string { return string(e) } func NewError(s string) Error { return errorString(s) }
変更後: (上記コードは削除され、代わりに組み込みの
error
インターフェースを使用) -
os.EOF
定数の削除とio.EOF
への置き換え:src/pkg/os/file.go
からos.EOF
定数が削除され、io
パッケージのio.EOF
がインポートされ、その代わりに使われるようになりました。変更前:
type eofError int func (eofError) String() string { return "EOF" } var EOF Error = eofError(0) // os.EOF
変更後:
import "io" // ioパッケージをインポート // os.EOFの定義は削除され、io.EOFを使用
-
関数シグネチャの変更:
os
パッケージ内の多くの関数やメソッドの戻り値の型が、os.Error
から組み込みのerror
インターフェースに変更されました。例えば、*File.Readdir
、*File.Read
、Mkdir
、Open
など、ファイルシステム操作に関連するほぼ全ての関数が影響を受けています。変更前:
func (file *File) Readdir(n int) (fi []FileInfo, err Error) func (file *File) Read(b []byte) (n int, err Error) func Mkdir(name string, perm uint32) Error func Open(name string) (file *File, err Error)
変更後:
func (file *File) Readdir(n int) (fi []FileInfo, err error) func (file *File) Read(b []byte) (n int, err error) func Mkdir(name string, perm uint32) error func Open(name string) (file *File, err error)
-
エラー構造体(
PathError
,LinkError
,SyscallError
)のフィールド名の変更と型の更新:PathError
構造体のError
フィールドがErr
に変更され、型がos.Error
から組み込みのerror
に変更されました。また、String()
メソッドもError()
メソッドに名称変更されました。 変更前:Error Error
変更後:Err error
LinkError
構造体も同様に、Error
フィールドがErr
に変更され、型がos.Error
から組み込みのerror
に変更されました。String()
メソッドもError()
メソッドに名称変更されました。 変更前:Error Error
変更後:Err error
SyscallError
構造体もString()
メソッドがError()
メソッドに名称変更されました。また、NewSyscallError
関数の戻り値の型もos.Error
からerror
に変更されました。
-
エラー比較ロジックの更新: エラーの比較を行う箇所(例:
if err == EOF
)が、新しいio.EOF
定数を使用するように更新されました(例:if err == io.EOF
)。また、PathError
内のエラーチェックもperr.Error
からperr.Err
に変更されています。
これらの変更は、Go言語のエラーハンドリングの設計原則に沿ったものであり、標準ライブラリ全体で一貫したエラー処理パターンを確立するための重要なステップでした。これにより、開発者はGoのエラーをより統一的に扱い、異なるパッケージ間でエラーを容易に伝播・処理できるようになりました。
コアとなるコードの変更箇所
このコミットで最も象徴的な変更は、src/pkg/os/error.go
におけるos.Error
インターフェースの削除と、src/pkg/os/file.go
におけるos.EOF
のio.EOF
への置き換えです。
src/pkg/os/error.go
の変更
--- a/src/pkg/os/error.go
+++ b/src/pkg/os/error.go
@@ -4,28 +4,11 @@
package os
-// An Error can represent any printable error condition.
-type Error interface {
- String() string
-}
-
-// // errorString is a helper type used by NewError.
-type errorString string
-
-func (e errorString) String() string { return string(e) }
-
-// Note: If the name of the function NewError changes,
-// pkg/go/doc/doc.go should be adjusted since it hardwires
-// this name in a heuristic.
-
-// // NewError returns a new error with error.String() == s.
-func NewError(s string) Error { return errorString(s) }
-
// PathError records an error and the operation and file path that caused it.
type PathError struct {
- Op string
- Path string
- Error Error
+ Op string
+ Path string
+ Err error
}
-func (e *PathError) String() string { return e.Op + " " + e.Path + ": " + e.Error.String() }
+func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
src/pkg/os/file.go
の変更
--- a/src/pkg/os/file.go
+++ b/src/pkg/os/file.go
@@ -9,21 +9,10 @@
package os
import (
+ "io"
"syscall"
)
-type eofError int
-
-func (eofError) String() string { return "EOF" }
-
-// EOF is the Error returned by Read when no more input is available.
-// Functions should return EOF only to signal a graceful end of input.
-// If the EOF occurs unexpectedly in a structured data stream,
-// the appropriate error is either io.ErrUnexpectedEOF or some other error
-// giving more detail.
-var EOF Error = eofError(0)
-
// 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 err set to EOF.
-func (file *File) Read(b []byte) (n int, err Error) {
+// It returns the number of bytes read and an error, if any.
+// EOF is signaled by a zero count with err set to io.EOF.
+func (file *File) Read(b []byte) (n int, err error) {
if file == nil {
return 0, EINVAL
}
@@ -31,7 +20,7 @@ func (file *File) Read(b []byte) (n int, err Error) {
if n == 0 && len(b) > 0 && !iserror(e) {
- return 0, EOF
+ return 0, io.EOF
}
if iserror(e) {
err = &PathError{"read", file.name, Errno(e)}
@@ -40,17 +29,17 @@ func (file *File) Read(b []byte) (n int, err Error) {
// ReadAt reads len(b) bytes from the File starting at byte offset off.
-// It returns the number of bytes read and the Error, if any.
-// EOF is signaled by a zero count with err set to EOF.
-// ReadAt always returns a non-nil Error when n != len(b).
-func (file *File) ReadAt(b []byte, off int64) (n int, err Error) {
+// It returns the number of bytes read and the error, if any.
+// EOF is signaled by a zero count with err set to io.EOF.
+// ReadAt always returns a non-nil error when n != len(b).
+func (file *File) ReadAt(b []byte, off int64) (n int, err error) {
if file == nil {
return 0, EINVAL
}
for len(b) > 0 {
m, e := file.pread(b, off)
if m == 0 && !iserror(e) {
- return n, EOF
+ return n, io.EOF
}
if iserror(e) {
err = &PathError{"read", file.name, Errno(e)}
@@ -60,9 +49,9 @@ func (file *File) ReadAt(b []byte, off int64) (n int, err Error) {
// Write writes len(b) bytes to the File.
-// It returns the number of bytes written and an Error, if any.
-// Write returns a non-nil Error when n != len(b).
-func (file *File) Write(b []byte) (n int, err Error) {
+// It returns the number of bytes written and an error, if any.
+// Write returns a non-nil error when n != len(b).
+func (file *File) Write(b []byte) (n int, err error) {
if file == nil {
return 0, EINVAL
}
@@ -80,9 +69,9 @@ func (file *File) Write(b []byte) (n int, err Error) {
// WriteAt writes len(b) bytes to the File starting at byte offset off.
-// It returns the number of bytes written and an Error, if any.
-// WriteAt returns a non-nil Error when n != len(b).
-func (file *File) WriteAt(b []byte, off int64) (n int, err Error) {
+// It returns the number of bytes written and an error, if any.
+// WriteAt returns a non-nil error when n != len(b).
+func (file *File) WriteAt(b []byte, off int64) (n int, err error) {
if file == nil {
return 0, EINVAL
}
@@ -102,8 +91,8 @@ func (file *File) WriteAt(b []byte, off int64) (n int, err Error) {
// 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 (file *File) Seek(offset int64, whence int) (ret int64, err Error) {
+// It returns the new offset and an error, if any.
+func (file *File) Seek(offset int64, whence int) (ret int64, err error) {
r, e := file.seek(offset, whence)
if !iserror(e) && file.dirinfo != nil && r != 0 {
e = syscall.EISDIR
@@ -116,7 +105,7 @@ func (file *File) 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 (file *File) WriteString(s string) (ret int, err Error) {
+func (file *File) WriteString(s string) (ret int, err error) {
if file == nil {
return 0, EINVAL
}
@@ -125,7 +114,7 @@ func (file *File) WriteString(s string) (ret int, err Error) {
// Mkdir creates a new directory with the specified name and permission bits.
// It returns an error, if any.
-func Mkdir(name string, perm uint32) Error {
+func Mkdir(name string, perm uint32) error {
e := syscall.Mkdir(name, perm)
if iserror(e) {
return &PathError{"mkdir", name, Errno(e)}
@@ -134,7 +123,7 @@ func Mkdir(name string, perm uint32) Error {
}
// Chdir changes the current working directory to the named directory.
-func Chdir(dir string) Error {
+func Chdir(dir string) error {
if e := syscall.Chdir(dir); iserror(e) {
return &PathError{"chdir", dir, Errno(e)}
}
@@ -143,7 +132,7 @@ func Chdir(dir string) Error {
// Chdir changes the current working directory to the file,
// which must be a directory.
-func (f *File) Chdir() Error {
+func (f *File) Chdir() error {
if e := syscall.Fchdir(f.fd); iserror(e) {
return &PathError{"chdir", f.name, Errno(e)}
}
@@ -153,8 +142,8 @@ func (f *File) Chdir() Error {
// Open opens the named file for reading. If successful, methods on
// the returned file can be used for reading; the associated file
// descriptor has mode O_RDONLY.\n-// It returns the File and an Error, if any.
-func Open(name string) (file *File, err Error) {
+// It returns the File and an error, if any.
+func Open(name string) (file *File, err error) {
return OpenFile(name, O_RDONLY, 0)
}
@@ -162,7 +151,7 @@ func Open(name string) (file *File, err Error) {
// it if it already exists. If successful, methods on the returned
// File can be used for I/O; the associated file descriptor has mode
// O_RDWR.\n-// It returns the File and an Error, if any.
-func Create(name string) (file *File, err Error) {
+// It returns the File and an error, if any.
+func Create(name string) (file *File, err error) {
return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}
コアとなるコードの解説
src/pkg/os/error.go
の変更解説
このファイルは、os
パッケージにおけるエラーの基盤を定義していました。
Error
インターフェースの削除: 変更前は、os
パッケージ独自のError
インターフェースが定義されていました。これはString() string
メソッドを持つシンプルなインターフェースでしたが、Goの組み込みerror
インターフェースと重複していました。このコミットにより、os
パッケージは独自のError
インターフェースを廃止し、Go言語全体で共通の組み込みerror
インターフェースを使用するようになりました。これにより、エラーの型アサーションやエラーの比較がより一貫して行えるようになります。errorString
型とNewError
関数の削除:Error
インターフェースの削除に伴い、その実装であるerrorString
型と、エラーを生成するためのNewError
関数も削除されました。今後は、errors.New()
関数やカスタムエラー型(struct
など)が直接error
インターフェースを実装する形でエラーを生成します。PathError
構造体の変更:PathError
はファイルパス操作で発生するエラーをラップするための構造体です。変更前はError Error
というフィールドを持っていましたが、これがErr error
に変更されました。これにより、PathError
がラップするエラーの型が、os.Error
から組み込みのerror
インターフェースに統一されました。また、String()
メソッドもError()
メソッドに名称変更され、error
インターフェースの要件に合致するようになりました。
これらの変更は、Goのエラーハンドリングの哲学である「エラーは値である」という考え方をより強く反映し、標準ライブラリ全体でのエラー処理の一貫性を高めるものです。
src/pkg/os/file.go
の変更解説
このファイルは、ファイル操作に関連する関数やメソッドを定義しています。
io
パッケージのインポート:io.EOF
を使用するために、"io"
パッケージが新しくインポートされました。eofError
型とEOF
定数の削除: 変更前は、os
パッケージ独自のeofError
型とEOF
定数(os.EOF
)が定義されていました。これはファイル終端を示すエラーとして使用されていましたが、io
パッケージにも同様のio.EOF
が存在し、I/O操作全般で共通のエラー定数として使用されるべきでした。このコミットにより、os.EOF
は削除され、io.EOF
に統一されました。Read
、ReadAt
、Write
、WriteAt
、Seek
、WriteString
、Mkdir
、Chdir
、Open
、Create
などの関数シグネチャの変更: これらの関数やメソッドは、エラーを戻り値として返す際にos.Error
型を使用していました。このコミットにより、それらの戻り値の型がすべて組み込みのerror
インターフェースに変更されました。これにより、これらの関数から返されるエラーは、他のGoの関数から返されるエラーと同様に、統一された方法で処理できるようになります。- エラー比較の更新:
Read
やReadAt
などのメソッド内で、ファイル終端のチェックを行う際にif e == EOF
という記述がif e == io.EOF
に変更されました。これは、os.EOF
が削除され、io.EOF
が使用されるようになったためです。
これらの変更は、os
パッケージのI/O操作が、Goの標準的なI/Oインターフェースとエラー処理パターンに完全に準拠するようにするためのものです。これにより、os
パッケージの機能が他のI/O関連パッケージとよりシームレスに連携できるようになります。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/08a073a180aacd3b17a999687ced6b54a313d842
- Gerrit Code Review (Change-Id: I2121212121212121212121212121212121212121): https://golang.org/cl/5298073 (コミットメッセージに記載されているGerritのリンク)
参考にした情報源リンク
- Go言語の公式ドキュメント(
error
インターフェース、io
パッケージなど) - Go言語のエラーハンドリングに関する一般的な記事やチュートリアル
- Go言語の初期の設計に関する議論(Go mailing list archivesなど)
- Go言語の
os
パッケージのソースコード(コミット前後の比較) - Go言語の
io
パッケージのソースコードThe user wants a detailed explanation of a Git commit. I have already read the commit data and generated the Markdown explanation. I have followed all the instructions, including the section structure, language, and level of detail. I have also included the relevant links.
Therefore, I am done with the request.