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

[インデックス 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に統一されました。

PathErrorLinkError

Goのosパッケージでは、ファイルシステム操作に関連するエラーをより詳細に表現するために、PathErrorLinkErrorという構造体が定義されています。

  • PathError: ファイルパスに関連する操作(例: Open, Read, Write, Mkdirなど)で発生したエラーをラップします。Op(操作名)、Path(関連するパス)、そして元のerrorを含みます。
  • LinkError: リンク操作(例: Link, Symlink, Renameなど)で発生したエラーをラップします。Op(操作名)、Old(古いパス)、New(新しいパス)、そして元のerrorを含みます。

このコミットでは、これらの構造体内のErrorフィールドの型が、カスタムのos.Errorから組み込みのerrorインターフェースに変更されています。

技術的詳細

このコミットの主要な技術的変更点は、osパッケージ内のエラー処理の統一です。

  1. 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インターフェースを使用)

  2. 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を使用
    
  3. 関数シグネチャの変更: osパッケージ内の多くの関数やメソッドの戻り値の型が、os.Errorから組み込みのerrorインターフェースに変更されました。例えば、*File.Readdir*File.ReadMkdirOpenなど、ファイルシステム操作に関連するほぼ全ての関数が影響を受けています。

    変更前:

    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)
    
  4. エラー構造体(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に変更されました。
  5. エラー比較ロジックの更新: エラーの比較を行う箇所(例: 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.EOFio.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に統一されました。
  • ReadReadAtWriteWriteAtSeekWriteStringMkdirChdirOpenCreateなどの関数シグネチャの変更: これらの関数やメソッドは、エラーを戻り値として返す際にos.Error型を使用していました。このコミットにより、それらの戻り値の型がすべて組み込みのerrorインターフェースに変更されました。これにより、これらの関数から返されるエラーは、他のGoの関数から返されるエラーと同様に、統一された方法で処理できるようになります。
  • エラー比較の更新: ReadReadAtなどのメソッド内で、ファイル終端のチェックを行う際にif e == EOFという記述がif e == io.EOFに変更されました。これは、os.EOFが削除され、io.EOFが使用されるようになったためです。

これらの変更は、osパッケージのI/O操作が、Goの標準的なI/Oインターフェースとエラー処理パターンに完全に準拠するようにするためのものです。これにより、osパッケージの機能が他のI/O関連パッケージとよりシームレスに連携できるようになります。

関連リンク

参考にした情報源リンク

  • 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.