WoJ
WoJ

Reputation: 29997

To what extent are errors strings guaranteed to not change?

One of the main issues I have with Golang is that the error handling is basically a check for a string (I would love to be wrong, do not hesitate :))

In the example below, I am trying to create a directory, but will have different behaviour depending on the kind of issue. Specifically, if a directory exists I will just pass.

package main

import (
    "fmt"
    "os"
)

func main() {
    err := os.Mkdir("test", 0644)
    if err != nil {
        fmt.Printf("error: %v", err)
        if err.Error() == "mkdir test: Cannot create a file when that file already exists" {
            fmt.Printf("the dir already exists")
        } else {
            panic(err)
        }
    }
}

It does not work, repeated attempts are not logged. Why? Ah, crap, I forgot the dot at the end of the mkdir test: Cannot create a file when that file already exists string.

I feel that relying on an error string is fragile, as opposed to having something like err.ErrorType().DirectoryExists() kind of check (which sometimes exists, in net for instance).

My question: to what extent can I rely on the fact that the error strings will not change? (in other words, that mkdir test: Cannot create a file when that file already exists. will not be worded differently, or ported to another national language, etc.)

I had some hope with errors.Is() but it ultimately relies on the string comparison.

Upvotes: 0

Views: 131

Answers (3)

Zyl
Zyl

Reputation: 3270

From https://pkg.go.dev/os#Mkdir:

Mkdir creates a new directory with the specified name and permission bits (before umask). If there is an error, it will be of type *PathError.

This means you could type-assert the returned error to get more information.

if err != nil {
    pathErr := err.(*os.PathError)
}

With errors returned from functions in package os specifically, also take note of these two functions:

to what extent can I rely on the fact that the error strings will not change?

To the extent which is guaranteed by the function's contract, which as in most programming languages conventionally is written in documenting comments above the function. In the case of os.MkDir(): you cannot.

Upvotes: 0

bcmills
bcmills

Reputation: 5197

Go error strings don't change arbitrarily, but they also aren't covered by the Go compatibility policy: they can be changed if the increase in clarity outweighs the (inevitable) cost of breaking programs that make (fragile, unsupported) assumptions about the string contents.

The errors package is the robust way to check for specific types of errors.

Use errors.Is to check for equivalence to a canonical error (https://play.golang.org/p/co6ukgQrr58):

    err := os.Mkdir(dir, 0644)
    if errors.Is(err, os.ErrExist) {
        t.Logf("the dir already exists")
    } else if err != nil {
        t.Fatal(err)
    }

Use errors.As to check for a particular type of error (https://play.golang.org/p/UR1nUCRMUY6):

    err := os.Mkdir(dir, 0644)
    var pe *os.PathError
    if errors.As(err, &pe) {
        t.Logf("error creating %q: %v", pe.Path, pe.Err)
    } else if err != nil {
        t.Fatal(err)
    }

Upvotes: 6

aureliar
aureliar

Reputation: 1594

In this case, you can use os.IsExist(err)

err := os.Mkdir("test", 0644)
if err != nil {
    if os.IsExist(err){
        fmt.Printf("the dir already exists")
    } else {
        panic(err)
    }
}

Good libraries should allow you to inspect errors without relying on string comparison. Various methods exist to do so:

  • Comparaison with sentinel values if err == os.EOF
  • Utility function: os.IsExist(err)
  • Type assertion: pathErr := err.(*os.PathError)

There is always a way to inspect errors in the standard library without relying on strings. Check the function/package documentation for details about how to do it.

Note:

errors.Is() and errors.As() are a (~recent) generalisation of == and type assertion but for errors that could contain other errors. See https://go.dev/blog/go1.13-errors

Upvotes: 4

Related Questions