[インデックス 12031] ファイルの概要
このコミットは、Go言語のos
パッケージにおけるLink
およびSymlink
関数が返すエラーの型について、ドキュメントを改善するものです。具体的には、これらの関数が*LinkError
型のエラーを返すことを明示的に記述し、file_plan9.go
においてはLinkError
を実際に返すように修正しています。これにより、APIの利用者がエラーハンドリングをより正確に行えるようになります。
コミット
commit a5f21c95dc279f421c094a592d65bf2ef89e87d6
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Sat Feb 18 04:45:43 2012 -0800
os: document the type of link errors
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5673090
---
src/pkg/os/file_plan9.go | 8 +++++---\n src/pkg/os/file_posix.go | 2 ++\n 2 files changed, 7 insertions(+), 3 deletions(-)\n
diff --git a/src/pkg/os/file_plan9.go b/src/pkg/os/file_plan9.go
index 70041f22aa..cb0e9ef928 100644
--- a/src/pkg/os/file_plan9.go
+++ b/src/pkg/os/file_plan9.go
@@ -276,7 +276,6 @@ func Remove(name string) error {
}\n \n // Rename renames a file.\n-// If there is an error, it will be of type *PathError.\n func Rename(oldname, newname string) error {\n \tvar d Dir\n \td.Null()\n@@ -340,12 +339,15 @@ func Pipe() (r *File, w *File, err error) {
// not supported on Plan 9\n \n // Link creates a hard link.\n+// If there is an error, it will be of type *LinkError.\n func Link(oldname, newname string) error {\n-\treturn ErrPlan9\n+\treturn &LinkError{\"link\", oldname, newname, ErrPlan9}\n }\n \n+// Symlink creates newname as a symbolic link to oldname.\n+// If there is an error, it will be of type *LinkError.\n func Symlink(oldname, newname string) error {\n-\treturn ErrPlan9\n+\treturn &LinkError{\"symlink\", oldname, newname, ErrPlan9}\n }\n \n func Readlink(name string) (string, error) {\ndiff --git a/src/pkg/os/file_posix.go b/src/pkg/os/file_posix.go
index 8861af1c7d..2ffc2ee083 100644
--- a/src/pkg/os/file_posix.go
+++ b/src/pkg/os/file_posix.go
@@ -38,6 +38,7 @@ func (e *LinkError) Error() string {
}\n \n // Link creates newname as a hard link to the oldname file.\n+// If there is an error, it will be of type *LinkError.\n func Link(oldname, newname string) error {\n \te := syscall.Link(oldname, newname)\n \tif e != nil {\n@@ -47,6 +48,7 @@ func Link(oldname, newname string) error {
}\n \n // Symlink creates newname as a symbolic link to oldname.\n+// If there is an error, it will be of type *LinkError.\n func Symlink(oldname, newname string) error {\n \te := syscall.Symlink(oldname, newname)\n \tif e != nil {\n```
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/a5f21c95dc279f421c094a592d65bf2ef89e87d6](https://github.com/golang/go/commit/a5f21c95dc279f421c094a592d65bf2ef89e87d6)
## 元コミット内容
os: document the type of link errors
R=golang-dev, r CC=golang-dev https://golang.org/cl/5673090
## 変更の背景
Go言語の`os`パッケージは、ファイルシステム操作のための基本的な機能を提供します。これらの関数は、操作が失敗した場合にエラーを返します。Goのエラーハンドリングの慣習として、特定の操作で発生しうるエラーの型をドキュメントに明記することは、APIの利用者がエラーの種類に応じて適切な処理(例えば、エラーの型アサーションやエラー値の比較)を行う上で非常に重要です。
このコミットの背景には、`os`パッケージの`Link`(ハードリンクの作成)および`Symlink`(シンボリックリンクの作成)関数が返すエラーの型が、ドキュメント上で明確にされていなかったという問題があります。既存の`Rename`関数には`*PathError`を返すことが明記されていましたが、リンク関連の関数には同様の記述がありませんでした。
特に、`file_plan9.go`の実装では、`Link`と`Symlink`が単に`ErrPlan9`という汎用的なエラーを返していました。これは、Plan 9システムではこれらの操作がサポートされていないことを示すものでしたが、Goのエラーハンドリングのベストプラクティスに従うと、ファイルパスに関連するエラーは`*PathError`または`*LinkError`のような構造化されたエラー型でラップされるべきです。これにより、エラーが発生した操作(`"link"`や`"symlink"`)、関連するパス(`oldname`, `newname`)、そして根本的なエラー(`ErrPlan9`)といった詳細情報をエラーオブジェクトに含めることができます。
このコミットは、これらの関数が`*LinkError`型のエラーを返すことをドキュメントに明記し、さらにPlan 9の実装においても`*LinkError`を適切に返すように修正することで、APIの一貫性とエラーハンドリングの利便性を向上させることを目的としています。
## 前提知識の解説
### Go言語のエラーハンドリング
Go言語では、エラーは組み込みの`error`インターフェースによって表現されます。関数がエラーを返す場合、通常は最後の戻り値として`error`型を返します。
```go
type error interface {
Error() string
}
エラーハンドリングは、通常if err != nil
という形式で行われます。
特定の種類のエラーを区別するために、Goではカスタムエラー型を定義し、error
インターフェースを実装させることが一般的です。これにより、エラー発生時にそのエラーの具体的な型をチェックし、より詳細な情報を取得したり、エラーの種類に応じた処理を行ったりすることが可能になります。これは、型アサーション(err.(*MyCustomError)
)や、Go 1.13以降で導入されたerrors.As
、errors.Is
関数を用いて行われます。
os
パッケージのエラー型
os
パッケージは、ファイルシステム操作に関連する特定のエラー型を定義しています。
-
*os.PathError
: ファイルパスに関連する操作(例:Open
,Stat
,Remove
,Rename
など)でエラーが発生した場合に返されるエラー型です。この構造体は、エラーが発生した操作名(Op
)、関連するファイルパス(Path
)、そして根本的なシステムコールエラー(Err
)を含みます。type PathError struct { Op string // 操作 (例: "open", "unlink") Path string // 操作対象のパス Err error // 根本的なエラー } func (e *PathError) Error() string
-
*os.LinkError
: ハードリンクやシンボリックリンクの作成(Link
,Symlink
)でエラーが発生した場合に返されるエラー型です。PathError
と同様に、操作名(Op
)、古いパス(Old
)、新しいパス(New
)、そして根本的なシステムコールエラー(Err
)を含みます。このコミット以前はos
パッケージのドキュメントで明示的に言及されていませんでしたが、このコミットによってその存在と使用が明確化されました。type LinkError struct { Op string // 操作 (例: "link", "symlink") Old string // 古いパス New string // 新しいパス Err error // 根本的なエラー } func (e *LinkError) Error() string
ハードリンクとシンボリックリンク
- ハードリンク (Hard Link): 同じiノード(ファイルシステム上のデータの実体)を指す、ファイルへの追加の参照です。元のファイルが削除されても、ハードリンクが存在する限りデータは削除されません。異なるファイルシステムをまたいで作成することはできません。
- シンボリックリンク (Symbolic Link / Soft Link): 別のファイルやディレクトリへのパスを指す特殊なファイルです。WindowsのショートカットやUnix/Linuxのシンボリックリンクに相当します。元のファイルが削除されると、シンボリックリンクは「壊れたリンク」となり、参照先を見失います。異なるファイルシステムをまたいで作成できます。
Plan 9とPOSIX
Go言語のos
パッケージは、様々なオペレーティングシステムに対応するために、OS固有の実装を持っています。
- POSIX: Portable Operating System Interfaceの略で、Unix系OSの標準インターフェースを定義したものです。Linux、macOS、BSDなどがこれに準拠しています。
file_posix.go
はこれらのシステム向けの実装を含みます。 - Plan 9: ベル研究所で開発された分散オペレーティングシステムです。Go言語の開発者の一部はPlan 9の経験があり、Goの設計思想にも影響を与えています。
file_plan9.go
はPlan 9システム向けの実装を含みます。Plan 9では一部のファイルシステム操作(例: ハードリンク)がサポートされていない場合があります。
技術的詳細
このコミットの主要な技術的詳細は、Go言語のエラーハンドリングにおける「エラーの型を明示する」というベストプラクティスをos
パッケージのLink
およびSymlink
関数に適用した点にあります。
GoのAPIドキュメントでは、関数が特定のカスタムエラー型を返す場合、そのことを明記することが推奨されます。これにより、開発者はswitch err.(type)
やerrors.As
などを用いて、エラーの種類に応じた分岐処理を記述できます。
変更前は、os.Link
とos.Symlink
のドキュメントには、エラーが返されることしか書かれていませんでした。しかし、実際にはこれらの関数は*os.LinkError
を返すことが期待されます。このコミットは、その期待をドキュメントに反映させました。
さらに重要なのは、file_plan9.go
における実装の変更です。Plan 9システムではハードリンクやシンボリックリンクがサポートされていないため、以前はこれらの関数が単にErrPlan9
という事前定義されたエラー値を返していました。しかし、これはLinkError
という構造化されたエラー型が提供する詳細情報(操作名、古いパス、新しいパス)を失うことになります。
このコミットでは、file_plan9.go
のLink
およびSymlink
関数が、ErrPlan9
を内部エラーとして含む*os.LinkError
インスタンスを返すように修正されました。
// 変更前 (file_plan9.go)
func Link(oldname, newname string) error {
return ErrPlan9
}
func Symlink(oldname, newname string) error {
return ErrPlan9
}
// 変更後 (file_plan9.go)
func Link(oldname, newname string) error {
return &LinkError{"link", oldname, newname, ErrPlan9}
}
func Symlink(oldname, newname string) error {
return &LinkError{"symlink", oldname, newname, ErrPlan9}
}
この変更により、Plan 9環境でリンク操作が失敗した場合でも、エラーを処理する側は*os.LinkError
としてエラーを受け取り、どの操作("link"
または"symlink"
)が、どのパス(oldname
, newname
)で失敗し、その根本的な理由がErrPlan9
である、という詳細な情報を取得できるようになります。これは、エラーログの改善や、より堅牢なエラーハンドリングロジックの実装に貢献します。
file_posix.go
に関しては、既にsyscall.Link
やsyscall.Symlink
が返すエラーを*os.LinkError
でラップして返していたため、ドキュメントの追加のみが行われました。これは、POSIXシステムではこれらの操作が通常サポートされており、システムコールが返すエラーをGoの慣習に沿った形でラップすることが適切であるためです。
コアとなるコードの変更箇所
diff --git a/src/pkg/os/file_plan9.go b/src/pkg/os/file_plan9.go
index 70041f22aa..cb0e9ef928 100644
--- a/src/pkg/os/file_plan9.go
+++ b/src/pkg/os/file_plan9.go
@@ -276,7 +276,6 @@ func Remove(name string) error {
}\n \n // Rename renames a file.\n-// If there is an error, it will be of type *PathError.\n func Rename(oldname, newname string) error {\n \tvar d Dir\n \td.Null()\n@@ -340,12 +339,15 @@ func Pipe() (r *File, w *File, err error) {
// not supported on Plan 9\n \n // Link creates a hard link.\n+// If there is an error, it will be of type *LinkError.\n func Link(oldname, newname string) error {\n-\treturn ErrPlan9\n+\treturn &LinkError{\"link\", oldname, newname, ErrPlan9}\n }\n \n+// Symlink creates newname as a symbolic link to oldname.\n+// If there is an error, it will be of type *LinkError.\n func Symlink(oldname, newname string) error {\n-\treturn ErrPlan9\n+\treturn &LinkError{\"symlink\", oldname, newname, ErrPlan9}\n }\n \n func Readlink(name string) (string, error) {\ndiff --git a/src/pkg/os/file_posix.go b/src/pkg/os/file_posix.go
index 8861af1c7d..2ffc2ee083 100644
--- a/src/pkg/os/file_posix.go
+++ b/src/pkg/os/file_posix.go
@@ -38,6 +38,7 @@ func (e *LinkError) Error() string {
}\n \n // Link creates newname as a hard link to the oldname file.\n+// If there is an error, it will be of type *LinkError.\n func Link(oldname, newname string) error {\n \te := syscall.Link(oldname, newname)\n \tif e != nil {\n@@ -47,6 +48,7 @@ func Link(oldname, newname string) error {
}\n \n // Symlink creates newname as a symbolic link to oldname.\n+// If there is an error, it will be of type *LinkError.\n func Symlink(oldname, newname string) error {\n \te := syscall.Symlink(oldname, newname)\n \tif e != nil {\n```
## コアとなるコードの解説
このコミットは主に2つのファイル、`src/pkg/os/file_plan9.go`と`src/pkg/os/file_posix.go`に変更を加えています。
### `src/pkg/os/file_plan9.go`の変更
1. **`Rename`関数のコメント削除**:
`// If there is an error, it will be of type *PathError.`
この行が削除されています。これは、`Rename`関数が`*PathError`を返すことは既に確立された慣習であり、他の多くのファイル操作関数と同様に自明であるため、冗長な記述と判断された可能性があります。または、このコミットの焦点が`LinkError`のドキュメント化にあるため、関連性の低いコメントを整理したのかもしれません。
2. **`Link`関数のドキュメントと実装の変更**:
* ドキュメントに`// If there is an error, it will be of type *LinkError.`という行が追加されました。これにより、`Link`関数が`*LinkError`を返すことが明示されます。
* 実装が`return ErrPlan9`から`return &LinkError{"link", oldname, newname, ErrPlan9}`に変更されました。これは、Plan 9システムでハードリンクがサポートされていない場合でも、`LinkError`構造体を使ってエラーの詳細情報(操作名、古いパス、新しいパス、そして根本的なエラー`ErrPlan9`)をカプセル化して返すようにしたものです。これにより、エラーハンドリングの際に、より豊富なコンテキストが得られるようになります。
3. **`Symlink`関数のドキュメントと実装の変更**:
* `Link`関数と同様に、ドキュメントに`// If there is an error, it will be of type *LinkError.`が追加されました。
* 実装が`return ErrPlan9`から`return &LinkError{"symlink", oldname, newname, ErrPlan9}`に変更されました。これも`Link`関数と同様に、Plan 9システムでシンボリックリンクがサポートされていない場合でも、`LinkError`構造体を使ってエラーの詳細情報を返すようにしたものです。
### `src/pkg/os/file_posix.go`の変更
1. **`Link`関数のドキュメント追加**:
`// If there is an error, it will be of type *LinkError.`という行が追加されました。POSIXシステムでは、`Link`関数は内部で`syscall.Link`を呼び出し、その結果のエラーを`*LinkError`でラップして返していました。この変更は、その既存の振る舞いをドキュメントに明記したものです。
2. **`Symlink`関数のドキュメント追加**:
`Link`関数と同様に、`// If there is an error, it will be of type *LinkError.`という行が追加されました。`Symlink`関数も内部で`syscall.Symlink`を呼び出し、その結果のエラーを`*LinkError`でラップして返していたため、その振る舞いをドキュメントに反映させたものです。
これらの変更により、`os`パッケージの`Link`および`Symlink`関数は、どのOSの実装においても一貫して`*os.LinkError`を返すことがドキュメントで保証され、Plan 9の実装においてもその保証がコードレベルで実現されました。これにより、GoのファイルシステムAPIの堅牢性と使いやすさが向上しています。
## 関連リンク
* Go言語 `os` パッケージドキュメント: [https://pkg.go.dev/os](https://pkg.go.dev/os)
* `os.Link` 関数ドキュメント: [https://pkg.go.dev/os#Link](https://pkg.go.dev/os#Link)
* `os.Symlink` 関数ドキュメント: [https://pkg.go.dev/os#Symlink](https://pkg.go.dev/os#Symlink)
* `os.PathError` 型ドキュメント: [https://pkg.go.dev/os#PathError](https://pkg.go.dev/os#PathError)
* `os.LinkError` 型ドキュメント: [https://pkg.go.dev/os#LinkError](https://pkg.go.dev/os#LinkError)
* Go言語のエラーハンドリングに関する公式ブログ記事 (A Tour of Go - Errors): [https://go.dev/tour/basics/16](https://go.dev/tour/basics/16)
## 参考にした情報源リンク
* Go言語の公式ドキュメント (`pkg.go.dev`)
* Go言語のソースコード (`github.com/golang/go`)
* Go言語のエラーハンドリングに関する一般的な知識
* ハードリンクとシンボリックリンクに関する一般的な知識
* Plan 9オペレーティングシステムに関する一般的な知識