[インデックス 10368] Go syscallパッケージのエラーハンドリング改革
コミット
- コミットハッシュ:
c017a8299fec188913726e5c0d19e669bc4a2feb
- 作成者: Russ Cox rsc@golang.org
- 作成日: 2011年11月13日 22:44:52 -0500
- コミットメッセージ: "syscall: use error"
GitHub上でのコミットページへのリンク
このコミットは2011年のもので、当時はGitHubではなくGoogle Codeで管理されていました。コードレビューは https://golang.org/cl/5372080
で行われました。
元コミット内容
syscall: use error
- syscall (not os) now defines the Errno type.
- the low-level assembly functions Syscall, Syscall6, and so on
return Errno, not uintptr
- syscall wrappers all return error, not uintptr.
R=golang-dev, mikioh.mikioh, r, alex.brainman
CC=golang-dev
https://golang.org/cl/5372080
このコミットは、syscallパッケージのエラーハンドリングを根本的に改革する重要な変更です。対象ファイルは100を超える大規模なリファクタリングで、以下の変更が含まれています:
doc/progs/file.go
src/cmd/cgo/out.go
src/pkg/crypto/rand/rand_windows.go
src/pkg/crypto/tls/root_windows.go
src/pkg/exp/inotify/inotify_linux.go
src/pkg/net/
配下の多数のファイルsrc/pkg/os/
配下の多数のファイルsrc/pkg/syscall/
配下の多数のファイル
変更の背景
このコミットが行われた2011年は、Go言語の正式リリース(Go 1.0)に向けた重要な時期でした。Go 1.0は2012年3月にリリースされる予定であり、このコミットは言語の安定性とAPIの一貫性を向上させるための重要な改革の一部でした。
当時のGoは、システムコールの戻り値としてuintptrを使用していましたが、これはエラーハンドリングの観点から以下の問題を抱えていました:
- 型安全性の欠如: uintptrは汎用的な整数型であり、エラー状態を明示的に表現できない
- エラーの判定ロジックの複雑化: 各システムコールの戻り値を個別に解釈する必要がある
- 一貫性の欠如: osパッケージとsyscallパッケージでエラーハンドリングの方法が異なる
前提知識の解説
Go言語のエラーハンドリング
Go言語は、例外処理機能を持たず、エラーを値として扱う設計哲学を採用しています。これにより、エラーの発生箇所と処理箇所が明確になり、プログラムの制御フローが予測しやすくなります。
syscallパッケージの役割
syscallパッケージは、オペレーティングシステムのシステムコールへの低レベルアクセスを提供します。このパッケージは、osパッケージやnetパッケージなどの高レベルパッケージの基盤となっています。
Errno型の意味
Errno(Error Number)は、UNIXシステムにおけるエラーコードの標準的な表現方法です。各エラーコードは、特定のエラー状態を示す数値で、例えば:
- ENOENT (2): ファイルまたはディレクトリが存在しない
- EACCES (13): アクセスが拒否された
- EINVAL (22): 無効な引数
技術的詳細
この変更の核心は、syscallパッケージのエラーハンドリングを以下のように改革することです:
1. Errno型の定義場所の変更
変更前: osパッケージでErrno型を定義 変更後: syscallパッケージでErrno型を定義
これにより、エラーハンドリングの責任がより適切な場所に移動しました。syscallパッケージは低レベルなシステムコールを直接扱うため、エラーコードの定義もここで行うのが自然です。
2. 低レベル関数の戻り値型の変更
変更前: Syscall()
, Syscall6()
などの関数がuintptrを返す
変更後: これらの関数がErrno型を返す
// 変更前
func Syscall(trap, a1, a2, a3 uintptr) (r1, r2, err uintptr)
// 変更後
func Syscall(trap, a1, a2, a3 uintptr) (r1, r2, err Errno)
3. syscallラッパー関数の戻り値型の変更
変更前: syscallラッパー関数がuintptrを返す 変更後: syscallラッパー関数がerror インターフェースを返す
// 変更前
func Open(path string, mode int, perm uint32) (fd int, errno uintptr)
// 変更後
func Open(path string, mode int, perm uint32) (fd int, err error)
4. エラーインターフェースの実装
Errno型が error
インターフェースを実装するようになりました:
type Errno uintptr
func (e Errno) Error() string {
// エラーメッセージを返す実装
}
コアとなるコードの変更箇所
syscall_unix.go の変更
// 変更前
func Syscall(trap, a1, a2, a3 uintptr) (r1, r2, err uintptr)
// 変更後
func Syscall(trap, a1, a2, a3 uintptr) (r1, r2, err Errno)
syscall_windows.go の変更
// 変更前
func Syscall(trap, nargs, a1, a2, a3 uintptr) (r1, r2, err uintptr)
// 変更後
func Syscall(trap, nargs, a1, a2, a3 uintptr) (r1, r2, err Errno)
各種システムコールラッパーの変更
例えば、open
システムコールのラッパー:
// 変更前
func Open(path string, mode int, perm uint32) (fd int, errno uintptr) {
// ...
}
// 変更後
func Open(path string, mode int, perm uint32) (fd int, err error) {
// ...
}
コアとなるコードの解説
1. Errno型の定義
type Errno uintptr
func (e Errno) Error() string {
if 0 <= int(e) && int(e) < len(errors) {
s := errors[e]
if s != "" {
return s
}
}
return "errno " + itoa(int(e))
}
この実装により、Errno型はerror
インターフェースを満たし、エラーメッセージを提供できるようになりました。
2. システムコールラッパーの改善
func Open(path string, mode int, perm uint32) (fd int, err error) {
var _p0 *byte
_p0, err = BytePtrFromString(path)
if err != nil {
return
}
r0, _, e1 := Syscall(SYS_OPEN, uintptr(unsafe.Pointer(_p0)), uintptr(mode), uintptr(perm))
fd = int(r0)
if e1 != 0 {
err = e1
}
return
}
この変更により、エラーハンドリングがより直感的かつ一貫性のあるものになりました。
3. エラーの伝播
// osパッケージでの利用例
file, err := os.Open("filename")
if err != nil {
// syscallパッケージから伝播したエラーを処理
return err
}
syscallパッケージが返すエラーは、osパッケージを経由してアプリケーションレベルまで一貫した形で伝播されます。
関連リンク
参考にした情報源リンク
- Go Issues #37627 - Not all os/error's handled in syscall.Errno.Is
- Go エラーハンドリング Wiki
- Dave Cheney's Error Handling Articles
この変更は、Go言語のエラーハンドリング哲学を体現する重要な改革であり、現在のGoプログラムで使用されているエラーハンドリングパターンの基盤を築いた歴史的に重要なコミットです。