[インデックス 10772] ファイルの概要
このコミットは、Go言語の実験的なWindowsファイルシステム通知パッケージ exp/winfsnotify
におけるビルドエラーを修正するものです。具体的には、Windows API呼び出しからのエラーチェックのロジックを、Go言語のイディオムに沿った nil
チェックに変更しています。
コミット
commit 1c50c32af07047e2df0cac7fa1e56fed33649e31
Author: Yasuhiro Matsumoto <mattn.jp@gmail.com>
Date: Wed Dec 14 13:17:48 2011 +1100
exp/winfsnotify: fix build.
R=golang-dev, alex.brainman
CC=golang-dev
https://golang.org/cl/5483057
---
src/pkg/exp/winfsnotify/winfsnotify.go | 22 +++++++++++-----------\n 1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/src/pkg/exp/winfsnotify/winfsnotify.go b/src/pkg/exp/winfsnotify/winfsnotify.go
index d133740304..d47ffd1392 100644
--- a/src/pkg/exp/winfsnotify/winfsnotify.go
+++ b/src/pkg/exp/winfsnotify/winfsnotify.go
@@ -75,7 +75,7 @@ type Watcher struct {
// NewWatcher creates and returns a Watcher.
func NewWatcher() (*Watcher, error) {
port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
- if e != 0 {
+ if e != nil {
return nil, os.NewSyscallError("CreateIoCompletionPort", e)
}
w := &Watcher{
@@ -147,7 +147,7 @@ func (w *Watcher) RemoveWatch(path string) error {
func (w *Watcher) wakeupReader() error {
e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)
- if e != 0 {
+ if e != nil {
return os.NewSyscallError("PostQueuedCompletionStatus", e)
}
return nil
@@ -155,7 +155,7 @@ func (w *Watcher) wakeupReader() error {
func getDir(pathname string) (dir string, err error) {
attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname))
- if e != 0 {
+ if e != nil {
return "", os.NewSyscallError("GetFileAttributes", e)
}
if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
@@ -173,11 +173,11 @@ func getIno(path string) (ino *inode, err error) {
syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
nil, syscall.OPEN_EXISTING,
syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0)
- if e != 0 {
+ if e != nil {
return nil, os.NewSyscallError("CreateFile", e)
}
var fi syscall.ByHandleFileInformation
- if e = syscall.GetFileInformationByHandle(h, &fi); e != 0 {
+ if e = syscall.GetFileInformationByHandle(h, &fi); e != nil {
syscall.CloseHandle(h)
return nil, os.NewSyscallError("GetFileInformationByHandle", e)
}
@@ -222,7 +222,7 @@ func (w *Watcher) addWatch(pathname string, flags uint64) error {
}
watchEntry := w.watches.get(ino)
if watchEntry == nil {
- if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != 0 {
+ if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil {
syscall.CloseHandle(ino.handle)
return os.NewSyscallError("CreateIoCompletionPort", e)
}
@@ -295,7 +295,7 @@ func (w *Watcher) deleteWatch(watch *watch) {
// Must run within the I/O thread.
func (w *Watcher) startRead(watch *watch) error {
- if e := syscall.CancelIo(watch.ino.handle); e != 0 {
+ if e := syscall.CancelIo(watch.ino.handle); e != nil {
w.Error <- os.NewSyscallError("CancelIo", e)
w.deleteWatch(watch)
}
@@ -304,7 +304,7 @@ func (w *Watcher) startRead(watch *watch) error {
mask |= toWindowsFlags(m)
}
if mask == 0 {
- if e := syscall.CloseHandle(watch.ino.handle); e != 0 {
+ if e := syscall.CloseHandle(watch.ino.handle); e != nil {
w.Error <- os.NewSyscallError("CloseHandle", e)
}
delete(w.watches[watch.ino.volume], watch.ino.index)
@@ -312,7 +312,7 @@ func (w *Watcher) startRead(watch *watch) error {
}
e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
- if e != 0 {
+ if e != nil {
err := os.NewSyscallError("ReadDirectoryChanges", e)
if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
// Watched directory was probably removed
@@ -354,7 +354,7 @@ func (w *Watcher) readEvents() {
}
}
var err error
- if e := syscall.CloseHandle(w.port); e != 0 {
+ if e := syscall.CloseHandle(w.port); e != nil {
err = os.NewSyscallError("CloseHandle", e)
}
close(w.Event)
@@ -386,7 +386,7 @@ func (w *Watcher) readEvents() {
default:
w.Error <- os.NewSyscallError("GetQueuedCompletionPort", e)
continue
- case 0:
+ case nil:
}
var offset uint32
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1c50c32af07047e2df0cac7fa1e56fed33649e31
元コミット内容
このコミットは、exp/winfsnotify
パッケージのビルドを修正することを目的としています。具体的には、syscall
パッケージから返されるエラーのチェック方法を、Go言語の標準的なエラーハンドリングに合わせる変更が行われています。
変更の背景
Go言語では、エラーは error
インターフェースを実装する型として表現されます。関数がエラーを返さない場合、そのエラー値は nil
となります。しかし、このコミットが修正する前のコードでは、syscall
パッケージから返されるエラー e
を、C言語のような数値(0
が成功、非0
がエラー)として e != 0
と比較していました。
syscall
パッケージは、Windows APIなどのOSの低レベルな関数をGoから呼び出すためのブリッジを提供します。これらのAPIは通常、成功/失敗を示す数値やハンドルを返し、エラー情報は別途 GetLastError()
のような関数で取得されます。syscall
パッケージはこれらの低レベルなエラーをGoの error
インターフェースにラップして返します。
したがって、e
が error
型であるにもかかわらず e != 0
と比較することは、型システム上の不整合や論理的な誤りにつながり、ビルドエラーや実行時エラーの原因となっていました。このコミットは、この誤ったエラーチェックをGoのイディオムである e != nil
に修正することで、ビルドが通るようにし、かつ正しいエラーハンドリングを保証することを目的としています。
前提知識の解説
1. Go言語のエラーハンドリング
Go言語では、エラーは組み込みの error
インターフェースによって表現されます。
type error interface {
Error() string
}
関数がエラーを返す可能性がある場合、通常は最後の戻り値として error
型を返します。エラーが発生しなかった場合は nil
を返し、エラーが発生した場合は nil
ではない error
型の値を返します。エラーの有無をチェックするには、常に if err != nil { ... }
という形式を使用します。
2. syscall
パッケージ
syscall
パッケージは、Goプログラムからオペレーティングシステム(OS)の低レベルなシステムコールを直接呼び出すための機能を提供します。Windowsの場合、これはWin32 APIの関数呼び出しに相当します。
syscall
パッケージの関数は、OSのAPIが返す生の値(例: ハンドル、DWORD)と、Goの error
型を組み合わせて返すことがよくあります。例えば、syscall.CreateIoCompletionPort
のような関数は、成功時には有効なハンドルと nil
エラーを返し、失敗時には syscall.InvalidHandle
と非nil
のエラーを返します。
3. os.NewSyscallError
os.NewSyscallError
は、syscall
パッケージで発生したエラーを、よりGoの標準ライブラリに統合された os.SyscallError
型にラップするためのヘルパー関数です。これにより、システムコールエラーに関する追加情報(システムコール名と元のエラーコード)を提供できます。
4. Windows I/O Completion Ports (IOCP)
exp/winfsnotify
パッケージは、Windowsのファイルシステム変更通知メカニズムである ReadDirectoryChangesW
と、非同期I/Oを効率的に処理するための I/O Completion Ports (IOCP)
を利用していると考えられます。
CreateIoCompletionPort
: I/O完了ポートを作成または既存のポートにファイルハンドルを関連付けます。PostQueuedCompletionStatus
: I/O完了ポートにカスタムの完了パケットをポストします。GetQueuedCompletionStatus
: I/O完了ポートから完了パケットを取得します。ReadDirectoryChanges
: 指定されたディレクトリまたはサブツリー内の変更を監視します。CancelIo
: 指定されたファイルハンドルに対して発行された保留中のI/O操作をキャンセルします。CloseHandle
: 開いているオブジェクトハンドルを閉じます。
これらのAPIは、成功時には TRUE
(非ゼロ) を返すか、有効なハンドルを返し、失敗時には FALSE
(ゼロ) を返すか、INVALID_HANDLE_VALUE
を返し、エラーコードは GetLastError()
で取得するのが一般的です。syscall
パッケージはこれらの挙動をGoのエラーインターフェースにマッピングしています。
技術的詳細
このコミットの技術的な核心は、Go言語におけるエラー値の正しい扱い方にあります。
Goの error
インターフェースは、nil
をエラーがない状態として定義します。これは、C言語や他の言語で 0
や NULL
が成功を示す慣習とは異なります。
元のコードでは、syscall
パッケージの関数呼び出しの結果として返される error
型の変数 e
を、数値の 0
と比較していました (if e != 0
)。これはGoの型システムでは不正な比較であり、コンパイルエラーまたは予期せぬ実行時動作を引き起こす可能性があります。Goのコンパイラは、error
型と整数型 0
の直接比較を許可しないため、このコードはビルドエラーの原因となっていました。
このコミットは、この誤った比較を、Goのイディオムである if e != nil
に変更することで修正しています。これにより、e
が実際にエラーを表す非nil
値である場合にのみ、エラー処理ロジックが実行されるようになります。
また、readEvents
関数内の switch
ステートメントで、case 0:
を case nil:
に変更している箇所も同様の理由です。GetQueuedCompletionPort
から返されるエラー e
が nil
であるかどうかをチェックするために、0
ではなく nil
を使用するのが正しいGoの構文です。
この修正により、exp/winfsnotify
パッケージは、Windows APIからのエラーをGoの標準的な方法で正しく検出し、処理できるようになり、堅牢性と可読性が向上します。
コアとなるコードの変更箇所
変更はすべて src/pkg/exp/winfsnotify/winfsnotify.go
ファイル内で行われています。
主な変更パターンは以下の2種類です。
-
if e != 0
をif e != nil
に変更:NewWatcher
関数内 (syscall.CreateIoCompletionPort
の結果)wakeupReader
関数内 (syscall.PostQueuedCompletionStatus
の結果)getDir
関数内 (syscall.GetFileAttributes
の結果)getIno
関数内 (syscall.CreateFile
およびsyscall.GetFileInformationByHandle
の結果)addWatch
関数内 (syscall.CreateIoCompletionPort
の結果)startRead
関数内 (syscall.CancelIo
,syscall.CloseHandle
,syscall.ReadDirectoryChanges
の結果)readEvents
関数内 (syscall.CloseHandle
の結果)
-
case 0:
をcase nil:
に変更:readEvents
関数内のswitch e := <-w.Error:
ステートメント内
コアとなるコードの解説
変更された各行は、Windows API呼び出しの戻り値としてGoの error
型を受け取り、そのエラーの有無をチェックする部分です。
例えば、NewWatcher
関数では、syscall.CreateIoCompletionPort
がI/O完了ポートのハンドルとエラーを返します。
// 変更前
port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
if e != 0 { // ここで e は error 型なのに 0 と比較している
return nil, os.NewSyscallError("CreateIoCompletionPort", e)
}
// 変更後
port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
if e != nil { // 正しく error 型と nil を比較
return nil, os.NewSyscallError("CreateIoCompletionPort", e)
}
同様に、readEvents
関数内の switch
ステートメントでは、w.Error
チャンネルから受け取ったエラー e
の値に基づいて処理を分岐しています。
// 変更前
switch e := <-w.Error:
// ...
case 0: // ここで e は error 型なのに 0 と比較している
// ...
// 変更後
switch e := <-w.Error:
// ...
case nil: // 正しく error 型と nil を比較
// ...
これらの変更は、Go言語の基本的なエラーハンドリングの原則に準拠させるためのものであり、コードの正確性と信頼性を向上させます。
関連リンク
- Go言語の
error
インターフェースに関する公式ドキュメント: https://go.dev/blog/error-handling-and-go - Go言語の
syscall
パッケージに関するドキュメント: https://pkg.go.dev/syscall - Windows API (Win32 API) のドキュメント (Microsoft Learn): https://learn.microsoft.com/en-us/windows/win32/api/
- I/O Completion Ports (IOCP) の概念: https://learn.microsoft.com/en-us/windows/win32/fileio/i-o-completion-ports
参考にした情報源リンク
- Go言語の公式ドキュメント
syscall
パッケージのソースコードとドキュメント- Windows APIの公式ドキュメント
- Go言語のエラーハンドリングに関する一般的な慣習とベストプラクティス