[インデックス 10660] ファイルの概要
このコミットは、Go言語のsyscall
パッケージ、特にWindows固有のシステムコールにおいて、関数のエラー返却メカニズムを改善するものです。以前はエラーを示すためにuintptr
型が使用されていましたが、この変更によりGoの標準的なerror
インターフェースを返すように統一されます。これにより、エラーハンドリングが一貫性のある、よりGoらしい方法で行えるようになります。
コミット
commit ef65feda2afd3644c884c630d628e46b06082e4c
Author: Alex Brainman <alex.brainman@gmail.com>
Date: Thu Dec 8 12:07:21 2011 +1100
syscall: return error, not uintptr, when function returns error
R=rsc
CC=golang-dev
https://golang.org/cl/5450119
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ef65feda2afd3644c884c630d628e46b06082e4c
元コミット内容
このコミットの目的は、Goのsyscall
パッケージにおいて、Windowsシステムコールがエラーを返す際に、従来のuintptr
型ではなく、Goの標準的なerror
インターフェースを返すように変更することです。これにより、Goのエラーハンドリングの慣習に沿った、より自然で安全なエラー処理が可能になります。
変更の背景
Go言語は、エラーハンドリングにおいて多値戻り値とerror
インターフェースの使用を推奨しています。しかし、初期のGoのsyscall
パッケージ、特にWindows固有の実装では、システムコールが成功した場合は0、失敗した場合はエラーコードをuintptr
型で返すという、C言語やWindows APIの慣習に近い形式が採用されていました。
このアプローチにはいくつかの問題がありました。
- Goのエラーハンドリングとの不整合:
uintptr
でエラーを表現することは、Goの慣習であるif err != nil
によるエラーチェックと異なり、コードの可読性や一貫性を損ねていました。 - 型安全性と表現力不足:
uintptr
は単なるポインタまたは整数であり、それがエラーであることを明示的に示すものではありませんでした。エラーの種類や詳細な情報をカプセル化するGoのerror
インターフェースの利点を活かせませんでした。 - 移植性の問題: Windows固有の
uintptr
によるエラー表現は、他のOS(Linux, macOSなど)のsyscall
パッケージにおけるエラーハンドリングと異なり、コードの移植性を低下させる可能性がありました。
このコミットは、これらの問題を解決し、Goのエラーハンドリングのベストプラクティスに準拠するために行われました。
前提知識の解説
Go言語のエラーハンドリング
Go言語では、エラーは通常、関数の最後の戻り値としてerror
インターフェース型で返されます。エラーがない場合はnil
が返され、呼び出し元はif err != nil
という慣用句でエラーの有無をチェックします。
func doSomething() (resultType, error) {
// ... 処理 ...
if somethingWentWrong {
return zeroValue, errors.New("something went wrong")
}
return actualResult, nil
}
// 呼び出し側
res, err := doSomething()
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Result:", res)
uintptr
型
uintptr
は、ポインタを保持するのに十分な大きさの符号なし整数型です。Goでは、C言語のポインタやWindows APIのハンドルなど、低レベルのメモリ操作やシステムコールとの連携で一時的に使用されることがあります。しかし、これは型安全性が低く、通常のエラー表現には推奨されません。
syscall.Errno
型
Goのsyscall
パッケージには、OS固有のエラーコードを表現するためのErrno
型が存在します。これは、システムコールが返す数値エラーコードをGoの型システムでラップしたものです。このコミット以前は、Errno
はuintptr
のエイリアスとして定義され、そのError()
メソッドでエラーメッセージを生成していました。このコミットでは、Errno
がuintptr
から独立した型として定義され、error
インターフェースを実装するようになります。
Windows APIのエラーハンドリング
Windows API関数は、通常、成功/失敗を示す戻り値を持ち、詳細なエラー情報はGetLastError()
関数を呼び出すことで取得できるエラーコード(DWORD
型)として提供されます。このエラーコードは、システム定義のエラーメッセージに対応しています。
技術的詳細
このコミットの主要な変更点は、Goのsyscall
パッケージにおけるWindowsシステムコールの戻り値の型を、エラーを示すuintptr
やint
から、Goの標準的なerror
インターフェースに変更したことです。
具体的には、以下の点が変更されました。
-
//sys
ディレクティブの変更:src/pkg/exp/wingui/winapi.go
やsrc/pkg/syscall/syscall_windows.go
などのファイルで、システムコール関数の宣言に付与されている//sys
ディレクティブが修正されました。 例://sys GetModuleHandle(modname *uint16) (handle syscall.Handle, errno int) = GetModuleHandleW
が//sys GetModuleHandle(modname *uint16) (handle syscall.Handle, err error) = GetModuleHandleW
に変更されました。//sys RegOpenKeyEx(...) (regerrno uintptr) = advapi32.RegOpenKeyExW
が//sys RegOpenKeyEx(...) (regerrno error) = advapi32.RegOpenKeyExW
に変更されました。 これにより、mksyscall_windows.pl
スクリプトが生成するGoコードにおいて、エラーがerror
型として扱われるようになります。
-
syscall.Errno
型の再定義と実装:src/pkg/syscall/dll_windows.go
にあったErrno
型の定義がsrc/pkg/syscall/syscall_windows.go
に移動され、その定義がtype Errno uintptr
から、error
インターフェースを実装する具体的な型として再定義されました。Errno
型は、Error()
メソッドを持つことでerror
インターフェースを満たします。このError()
メソッドは、Windows APIのFormatMessage
関数を呼び出して、数値エラーコードに対応する人間が読めるエラーメッセージを取得します。 -
システムコールラッパーの変更:
src/pkg/exp/wingui/zwinapi.go
、src/pkg/syscall/zsyscall_windows_386.go
、src/pkg/syscall/zsyscall_windows_amd64.go
などの自動生成されるシステムコールラッパー関数が変更されました。- 以前は、システムコールが失敗した場合に
uintptr
型のエラーコードを直接返したり、errno int
として返したりしていました。 - 変更後は、システムコールが失敗した場合に、返されたエラーコード(
uintptr
)をsyscall.Errno
型にキャストし、それをerror
インターフェースとして返すようになりました。成功した場合はnil
を返します。 例:
// 変更前 (zwinapi.goのGetModuleHandleの一部) // if handle == 0 { // if e1 != 0 { // errno = int(e1) // } else { // errno = syscall.EINVAL // } // } else { // errno = 0 // } // 変更後 // if handle == 0 { // if e1 != 0 { // err = error(e1) // e1はsyscall.Errno型 // } else { // err = syscall.EINVAL // } // }
ここで
e1
はsyscall.Syscall
が返す第三の戻り値で、Windowsのエラーコード(uintptr
)です。これがsyscall.Errno
に変換され、最終的にerror
として返されます。 - 以前は、システムコールが失敗した場合に
-
呼び出し箇所の修正:
src/pkg/exp/wingui/gui.go
、src/pkg/mime/type_windows.go
、src/pkg/net/fd_windows.go
、src/pkg/net/interface_windows.go
、src/pkg/net/lookup_windows.go
など、syscall
パッケージの関数を呼び出している箇所が修正されました。- 以前は
if e != 0
のように数値でエラーをチェックしていましたが、 - 変更後は
if e != nil
のようにGoの標準的なエラーチェックを行うようになりました。 - また、
os.NewSyscallError
の引数も、syscall.Errno(e)
のようにErrno
型にキャストして渡すように変更されています。
- 以前は
-
mksyscall_windows.pl
スクリプトの変更: このPerlスクリプトは、//sys
ディレクティブに基づいてGoのシステムコールラッパーコードを生成します。このコミットでは、スクリプトがerror
型の戻り値を正しく処理し、適切なエラーチェックとErrno
への変換を行うコードを生成するように修正されました。
これらの変更により、GoのWindowsシステムコールは、Goのエラーハンドリングの原則に完全に準拠するようになり、より堅牢で読みやすいコードが書けるようになりました。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、主に以下のファイルに集中しています。
-
src/pkg/syscall/syscall_windows.go
:Errno
型の定義がsrc/pkg/syscall/dll_windows.go
から移動され、Error()
メソッドの実装が追加されました。これにより、Errno
がerror
インターフェースを直接実装するようになりました。//sys
ディレクティブを持つ多くのシステムコール関数の宣言で、エラー戻り値の型がuintptr
やint
からerror
に変更されました。errstr
関数が削除され、そのロジックがErrno.Error()
メソッドに統合されました。
-
src/pkg/syscall/dll_windows.go
:Errno
型の定義が削除されました。
-
src/pkg/syscall/mksyscall_windows.pl
:- システムコールラッパーを生成するPerlスクリプトが修正され、
//sys
ディレクティブでerror
が指定された場合に、適切なエラーチェックとsyscall.Errno
への変換を行うGoコードを生成するようになりました。
- システムコールラッパーを生成するPerlスクリプトが修正され、
-
src/pkg/syscall/zsyscall_windows_386.go
およびsrc/pkg/syscall/zsyscall_windows_amd64.go
:mksyscall_windows.pl
によって自動生成されるファイルであり、多くのシステムコールラッパー関数において、エラーの戻り値がuintptr
からerror
に変更され、内部でsyscall.Errno
への変換が行われるようになりました。- 例:
GetLastError()
関数の戻り値がuintptr
からerror
に変更され、r0 != 0
の場合にErrno(r0)
を返すようになりました。
-
src/pkg/exp/wingui/gui.go
およびsrc/pkg/exp/wingui/winapi.go
:wingui
パッケージはWindows GUIの実験的な実装であり、多くのWindows APIを呼び出します。これらのファイルでは、システムコール関数の呼び出し箇所でエラーチェックがe != 0
からe != nil
に変更され、エラー変数の型もint
からerror
に変更されました。winapi.go
では、//sys
ディレクティブの変更が多数行われました。
-
src/pkg/mime/type_windows.go
、src/pkg/net/fd_windows.go
、src/pkg/net/interface_windows.go
、src/pkg/net/lookup_windows.go
:- これらのファイルでは、
syscall
パッケージの関数呼び出しにおけるエラーチェックが!= 0
から!= nil
に変更されました。
- これらのファイルでは、
コアとなるコードの解説
syscall/syscall_windows.go
の Errno
型の変更
// 変更前 (dll_windows.go にあった定義)
// type Errno uintptr
// func (e Errno) Error() string { ... }
// 変更後 (syscall_windows.go に移動)
type Errno uintptr
func (e Errno) Error() string {
// deal with special go errors
idx := int(e - APPLICATION_ERROR)
if 0 <= idx && idx < len(errors) {
return errors[idx]
}
// ask windows for the remaining errors
var flags uint32 = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY | FORMAT_MESSAGE_IGNORE_INSERTS
b := make([]uint16, 300)
n, err := FormatMessage(flags, 0, uint32(e), 0, b, nil)
if err != nil {
return "error " + itoa(int(e)) + " (FormatMessage failed with err=" + itoa(int(err.(Errno))) + ")"
}
// trim terminating \r and \n
for ; n > 0 && (b[n-1] == '\n' || b[n-1] == '\r'); n-- {
}
return string(utf16.Decode(b[:n]))
}
この変更により、syscall.Errno
はuintptr
のエイリアスでありながら、Error()
メソッドを実装することでGoのerror
インターフェースを満たすようになりました。Error()
メソッドは、Windows APIのFormatMessage
関数を使用して、数値エラーコードを人間が読める文字列に変換します。これにより、システムコールから返されるエラーが、Goの標準的なエラー処理メカニズムにシームレスに統合されます。
mksyscall_windows.pl
の変更
# 変更前 (一部抜粋)
# if ($rettype eq "errno") {
# $body .= "\t\t\t$name = int(e1)\\n";
# }
# 変更後 (一部抜粋)
# ...
# } elsif($rettype eq "error") {
# # Set $reg to "error" only if returned value indicate failure
# $body .= "\\tif $reg != 0 {\\n";
# $body .= "\\t\\t$name = Errno($reg)\\n";
# $body .= "\\t}\\n";
# }
このPerlスクリプトの変更は、//sys
ディレクティブで戻り値の型がerror
と指定された場合に、生成されるGoコードが、システムコールからの生の戻り値(uintptr
)をsyscall.Errno
型にキャストし、それをerror
として返すように指示します。これにより、自動生成されるラッパー関数がGoのエラーハンドリング規約に準拠するようになります。
自動生成される zsyscall_windows_*.go
ファイルの変更例
zsyscall_windows_386.go
や zsyscall_windows_amd64.go
のようなファイルでは、各システムコールラッパー関数が以下のように変更されました。
// 変更前 (例: GetLastError)
// func GetLastError() (lasterr uintptr) {
// r0, _, _ := Syscall(procGetLastError.Addr(), 0, 0, 0, 0)
// lasterr = uintptr(r0)
// return
// }
// 変更後 (例: GetLastError)
func GetLastError() (lasterr error) {
r0, _, _ := Syscall(procGetLastError.Addr(), 0, 0, 0, 0)
if r0 != 0 {
lasterr = Errno(r0)
}
return
}
この変更により、GetLastError
のような関数は、エラーがない場合はnil
を返し、エラーがある場合はsyscall.Errno
型のインスタンス(これはerror
インターフェースを満たす)を返すようになりました。これにより、呼び出し元はif err != nil
というGoの標準的な方法でエラーをチェックできます。
wingui
パッケージでのエラーチェックの変更例
src/pkg/exp/wingui/gui.go
のようなファイルでは、システムコール呼び出し後のエラーチェックが変更されました。
// 変更前
// var e int
// ...
// if e != 0 {
// abortErrNo("CreateWindowEx", e)
// }
// 変更後
// var e error
// ...
// if e != nil {
// abortErrNo("CreateWindowEx", e)
// }
この変更は、syscall
パッケージの関数がerror
インターフェースを返すようになったことに伴い、呼び出し側もその変更に適応したことを示しています。これにより、Goのコード全体で一貫したエラーハンドリングが実現されます。
関連リンク
- Go言語のエラーハンドリングに関する公式ドキュメントやブログ記事(当時のものがあれば)
- Windows APIのエラーコードに関するMicrosoftのドキュメント
- Goの
syscall
パッケージのドキュメント
参考にした情報源リンク
- Go言語の公式ドキュメント: https://go.dev/
- Go言語の
syscall
パッケージのドキュメント: https://pkg.go.dev/syscall - Windows APIに関するMicrosoft Learn: https://learn.microsoft.com/en-us/windows/win32/api/
- Goのコミット履歴 (GitHub): https://github.com/golang/go/commits/master
- Goのコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージに記載されている
https://golang.org/cl/5450119
は、Gerritの変更リストへのリンクです。) - Goの
mksyscall
ツールに関する情報 (Goのソースコード内のドキュメントや関連する設計ドキュメント)