Menu
Coddy logo textTech

errors.Is() と errors.As()

CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 56/107。

エラーがラップされると、err == ErrNotFound のような単純な等価性のチェックは機能しなくなります。errors パッケージは、ラップされたエラーチェーンを検査するための2つの関数、errors.Is()errors.As() を提供しています。

errors.Is() は、エラーチェーン内のいずれかのエラーが特定のセンチネルエラーと一致するかどうかを確認します:

import "errors"

var ErrNotFound = errors.New("not found")

func FindUser(id int) error {
    return fmt.Errorf("database lookup failed: %w", ErrNotFound)
}

err := FindUser(42)
if errors.Is(err, ErrNotFound) {
    fmt.Println("User doesn't exist")
}

返されたエラーがラップされていても、errors.Is() はチェーンをアンラップして、内部にある ErrNotFound を見つけます。

errors.As() は、エラーチェーン内のいずれかのエラーが特定の*型*に一致するかどうかを確認し、それを抽出します:

type ValidationError struct {
    Field string
}

func (e *ValidationError) Error() string {
    return "validation failed: " + e.Field
}

err := fmt.Errorf("request failed: %w", &ValidationError{Field: "email"})

var ve *ValidationError
if errors.As(err, &ve) {
    fmt.Println("Invalid field:", ve.Field)
}

センチネルエラーをチェックする場合は errors.Is() を使用し、カスタムエラー型のフィールドにアクセスする必要がある場合は errors.As() を使用します。どちらの関数もエラーチェーン全体を走査するため、ラップされたエラーを適切に処理するために不可欠です。

challenge icon

チャレンジ

簡単

errors.Is()errors.As() を使用して、ラップされたエラーチェーンを適切に検査する方法を示すファイル処理システムを構築しましょう。異なるレイヤーを通過する際にラップされるエラーを作成し、これらの関数を使用してエラーを特定し、そこから情報を抽出します。

コードは2つのファイルに分けて構成します:

  • errors.go: ファイルシステムの型エラーインフラストラクチャを定義します。

    センチネルエラーを作成します:

    • ErrPermissionDenied、メッセージは "permission denied"

    カスタムエラー型を作成します:

    • FileError 構造体、Filename (string) と Operation (string) フィールドを持つ
    • その Error() メソッドは [Operation] failed on file: [Filename] を返す必要があります

    ファイル操作をシミュレートし、エラーをラップする2つの関数を実装します:

    • ReadFile(filename string) error - filename が "secret.txt" の場合、ErrPermissionDenied を返します。filename が "missing.txt" の場合、Operation が "read" で指定された Filename を持つ *FileError を返します。それ以外の場合は nil を返します。
    • ProcessFile(filename string) error - ReadFile を呼び出します。エラーが返された場合、fmt.Errorf を使用して "processing failed: %w" という形式でラップします。それ以外の場合は nil を返します。
  • main.go: 入力からファイル名を読み取り、ProcessFile を呼び出します。errors.Is()errors.As() を使用してラップされたエラーチェーンを検査し、内部で見つかった内容に基づいて適切なメッセージを出力します。

以下の入力が提供されます:

  • 1行目:処理するファイル名

エラーチェーンを検査して結果を処理します:

  • errors.Is() がチェーン内で ErrPermissionDenied を見つけた場合:完全なエラーメッセージを出力し、次の行に Access denied - check file permissions と出力します。
  • errors.As() がチェーン内で *FileError を見つけた場合:完全なエラーメッセージを出力し、次の行に File issue: [Filename] during [Operation] と出力します。
  • エラーが発生しなかった場合:File '[filename]' processed successfully と出力します。

例えば、secret.txt が与えられた場合、出力は以下のようになります:

processing failed: permission denied
Access denied - check file permissions

また、missing.txt が与えられた場合、出力は以下のようになります:

processing failed: read failed on file: missing.txt
File issue: missing.txt during read

そして、data.txt が与えられた場合、出力は以下のようになります:

File 'data.txt' processed successfully

エラーが ProcessFile によってラップされているにもかかわらず、errors.Is() で元のセンチネルエラーを検出し、errors.As() でカスタムエラー型を抽出できることに注目してください。これらの関数は、エラーチェーン全体をトラバースして、探しているものを見つけ出します。

チートシート

エラーがラップされている場合は、errorsパッケージのerrors.Is()errors.As()を使用してエラーチェーンを検査します。

errors.Is()は、チェーン内のいずれかのエラーがセンチネルエラーと一致するかどうかを確認します:

import "errors"

var ErrNotFound = errors.New("not found")

err := fmt.Errorf("database lookup failed: %w", ErrNotFound)
if errors.Is(err, ErrNotFound) {
    // ErrNotFoundがチェーン内で見つかりました
}

errors.As()は、チェーン内のいずれかのエラーが特定の型と一致するかどうかを確認し、それを抽出します:

type ValidationError struct {
    Field string
}

func (e *ValidationError) Error() string {
    return "validation failed: " + e.Field
}

err := fmt.Errorf("request failed: %w", &ValidationError{Field: "email"})

var ve *ValidationError
if errors.As(err, &ve) {
    // veにチェーン内のValidationErrorが含まれるようになりました
    fmt.Println(ve.Field)
}

センチネルエラーにはerrors.Is()を使用し、カスタムエラー型のフィールドにアクセスする必要がある場合にはerrors.As()を使用します。どちらの関数もエラーチェーン全体を走査します。

自分で試してみよう

package main

import (
	"errors"
	"fmt"
)

func main() {
	// 入力からファイル名を読み込む
	var filename string
	fmt.Scanln(&filename)

	// ファイル名を使用してProcessFileを呼び出す
	err := ProcessFile(filename)

	// TODO: エラーチェーンを検査して結果を処理する
	// 
	// errors.Is() を使用して、ErrPermissionDenied がチェーン内にあるか確認する:
	// - 見つかった場合:エラーの全文を出力し、次に "Access denied - check file permissions" を出力する
	//
	// errors.As() を使用して、*FileError がチェーン内にあるか確認する:
	// - 見つかった場合:エラーの全文を出力し、次に "File issue: [Filename] during [Operation]" を出力する
	//
	// エラーがない場合:"File '[filename]' processed successfully" を出力する

	_ = errors.Is // ヒント: errors.Is() を使用する
	_ = errors.As // ヒント: errors.As() を使用する
	_ = err
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン