Как создать ошибку golang

Package errors implements functions to manipulate errors.

The New function creates errors whose only content is a text message.

An error e wraps another error if e’s type has one of the methods

Unwrap() error
Unwrap() []error

If e.Unwrap() returns a non-nil error w or a slice containing w,
then we say that e wraps w. A nil error returned from e.Unwrap()
indicates that e does not wrap any error. It is invalid for an
Unwrap method to return an []error containing a nil error value.

An easy way to create wrapped errors is to call fmt.Errorf and apply
the %w verb to the error argument:

wrapsErr := fmt.Errorf("... %w ...", ..., err, ...)

Successive unwrapping of an error creates a tree. The Is and As
functions inspect an error’s tree by examining first the error
itself followed by the tree of each of its children in turn
(pre-order, depth-first traversal).

Is examines the tree of its first argument looking for an error that
matches the second. It reports whether it finds a match. It should be
used in preference to simple equality checks:

if errors.Is(err, fs.ErrExist)

is preferable to

if err == fs.ErrExist

because the former will succeed if err wraps fs.ErrExist.

As examines the tree of its first argument looking for an error that can be
assigned to its second argument, which must be a pointer. If it succeeds, it
performs the assignment and returns true. Otherwise, it returns false. The form

var perr *fs.PathError
if errors.As(err, &perr) {
	fmt.Println(perr.Path)
}

is preferable to

if perr, ok := err.(*fs.PathError); ok {
	fmt.Println(perr.Path)
}

because the former will succeed if err wraps an *fs.PathError.

package main

import (
	"fmt"
	"time"
)

// MyError is an error implementation that includes a time and message.
type MyError struct {
	When time.Time
	What string
}

func (e MyError) Error() string {
	return fmt.Sprintf("%v: %v", e.When, e.What)
}

func oops() error {
	return MyError{
		time.Date(1989, 3, 15, 22, 30, 0, 0, time.UTC),
		"the file system has gone away",
	}
}

func main() {
	if err := oops(); err != nil {
		fmt.Println(err)
	}
}
Output:

1989-03-15 22:30:00 +0000 UTC: the file system has gone away
  • func As(err error, target any) bool
  • func Is(err, target error) bool
  • func Join(errs …error) error
  • func New(text string) error
  • func Unwrap(err error) error
  • Package
  • As
  • Is
  • Join
  • New
  • New (Errorf)
  • Unwrap

This section is empty.

This section is empty.

As finds the first error in err’s tree that matches target, and if one is found, sets
target to that error value and returns true. Otherwise, it returns false.

The tree consists of err itself, followed by the errors obtained by repeatedly
calling Unwrap. When err wraps multiple errors, As examines err followed by a
depth-first traversal of its children.

An error matches target if the error’s concrete value is assignable to the value
pointed to by target, or if the error has a method As(interface{}) bool such that
As(target) returns true. In the latter case, the As method is responsible for
setting target.

An error type might provide an As method so it can be treated as if it were a
different error type.

As panics if target is not a non-nil pointer to either a type that implements
error, or to any interface type.

package main

import (
	"errors"
	"fmt"
	"io/fs"
	"os"
)

func main() {
	if _, err := os.Open("non-existing"); err != nil {
		var pathError *fs.PathError
		if errors.As(err, &pathError) {
			fmt.Println("Failed at path:", pathError.Path)
		} else {
			fmt.Println(err)
		}
	}

}
Output:

Failed at path: non-existing

Is reports whether any error in err’s tree matches target.

The tree consists of err itself, followed by the errors obtained by repeatedly
calling Unwrap. When err wraps multiple errors, Is examines err followed by a
depth-first traversal of its children.

An error is considered to match a target if it is equal to that target or if
it implements a method Is(error) bool such that Is(target) returns true.

An error type might provide an Is method so it can be treated as equivalent
to an existing error. For example, if MyError defines

func (m MyError) Is(target error) bool { return target == fs.ErrExist }

then Is(MyError{}, fs.ErrExist) returns true. See syscall.Errno.Is for
an example in the standard library. An Is method should only shallowly
compare err and the target and not call Unwrap on either.

package main

import (
	"errors"
	"fmt"
	"io/fs"
	"os"
)

func main() {
	if _, err := os.Open("non-existing"); err != nil {
		if errors.Is(err, fs.ErrNotExist) {
			fmt.Println("file does not exist")
		} else {
			fmt.Println(err)
		}
	}

}
Output:

file does not exist

Join returns an error that wraps the given errors.
Any nil error values are discarded.
Join returns nil if errs contains no non-nil values.
The error formats as the concatenation of the strings obtained
by calling the Error method of each element of errs, with a newline
between each string.

package main

import (
	"errors"
	"fmt"
)

func main() {
	err1 := errors.New("err1")
	err2 := errors.New("err2")
	err := errors.Join(err1, err2)
	fmt.Println(err)
	if errors.Is(err, err1) {
		fmt.Println("err is err1")
	}
	if errors.Is(err, err2) {
		fmt.Println("err is err2")
	}
}
Output:

err1
err2
err is err1
err is err2

New returns an error that formats as the given text.
Each call to New returns a distinct error value even if the text is identical.

package main

import (
	"errors"
	"fmt"
)

func main() {
	err := errors.New("emit macho dwarf: elf header corrupted")
	if err != nil {
		fmt.Print(err)
	}
}
Output:

emit macho dwarf: elf header corrupted

The fmt package’s Errorf function lets us use the package’s formatting
features to create descriptive error messages.

package main

import (
	"fmt"
)

func main() {
	const name, id = "bimmler", 17
	err := fmt.Errorf("user %q (id %d) not found", name, id)
	if err != nil {
		fmt.Print(err)
	}
}
Output:

user "bimmler" (id 17) not found

Unwrap returns the result of calling the Unwrap method on err, if err’s
type contains an Unwrap method returning error.
Otherwise, Unwrap returns nil.

Unwrap returns nil if the Unwrap method returns []error.

package main

import (
	"errors"
	"fmt"
)

func main() {
	err1 := errors.New("error1")
	err2 := fmt.Errorf("error2: [%w]", err1)
	fmt.Println(err2)
	fmt.Println(errors.Unwrap(err2))
	// Output
	// error2: [error1]
	// error1
}
Output:

This section is empty.

Строковые ошибки

Стандартная библиотека предлагает два готовых варианта.

// простая строковая ошибка
err1 := errors.New("math: square root of negative number")

// с форматированием
err2 := fmt.Errorf("math: square root of negative number %g", x)

Пользовательские ошибки с данными

Чтобы определить пользовательский тип ошибки, вы должны реализовать предварительно объявленный интерфейс error.

type error interface {
    Error() string
}

Вот два примера.

type SyntaxError struct {
    Line int
    Col  int
}

func (e *SyntaxError) Error() string {
    return fmt.Sprintf("%d:%d: syntax error", e.Line, e.Col)
}
type InternalError struct {
    Path string
}

func (e *InternalError) Error() string {
    return fmt.Sprintf("parse %v: internal error", e.Path)
}

Если Foo является функцией, которая может возвращать SyntaxError или InternalError, вы можете обрабатывать два случая, как показано ниже:

if err := Foo(); err != nil {
    switch e := err.(type) {
    case *SyntaxError:
        // Делаем что-нибудь интересное с e.Line и e.Col.
    case *InternalError:
        // Прервать и записать проблему.
    default:
        log.Println(e)
    }
}

Читайте также:

  • Работа с ошибками в Go 1.13
  • Основы Go: ошибки
  • Эффективный Go: ошибки
  • Обработка ошибок в Golang

Overview

Creating a custom error in your codes can be very useful and give you a better description of that error. So, in this shot, we will learn how to create custom errors using the errors package.

What is the errors package?

errors is a package that contains methods for manipulating errors.

What is the New method?

The New method generates errors with merely a text message as their content.

Syntax for the New method

The syntax for this method is New (Errorf).

Parameter for the New method

You basically pass in the description of the error as a parameter.

Example

For test purposes, to create a scenario we use an if statement to check a number, and then we create our error.

Welcome to tutorial no. 31 in our Golang tutorial series.

In the last tutorial we learnt about error representation in Go and how to handle errors from the standard library. We also learnt how to extract more information from the errors.

This tutorial deals with how to create our own custom errors which we can use in our functions and packages. We will also use the same techniques employed by the standard library to provide more details about our custom errors.

Creating custom errors using the New function

The simplest way to create a custom error is to use the New function of the errors package.

Before we use the New function to create a custom error, let’s understand how it is implemented. The implementation of the New function in the errors package is provided below.

package errors

// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
func New(text string) error {  
        return &errorString{text}
}

// errorString is a trivial implementation of error.
type errorString struct {  
        s string
}

func (e *errorString) Error() string {  
        return e.s
}

The implementation is pretty simple. errorString is a struct type with a single string field s. The Error() string method of the error interface is implemented using a errorString pointer receiver in line no. 14.

The New function in line no. 5 takes a string parameter, creates a value of type errorString using that parameter and returns the address of it. Thus a new error is created and returned.

Now that we know how the New function works, lets use it in a program of our own to create a custom error.

We will create a simple program which calculates the area of a circle and will return an error if the radius is negative.

package main

import (  
    "errors"
    "fmt"
    "math"
)

func circleArea(radius float64) (float64, error) {  
    if radius < 0 {
        return 0, errors.New("Area calculation failed, radius is less than zero")
    }
    return math.Pi * radius * radius, nil
}

func main() {  
    radius := -20.0
    area, err := circleArea(radius)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("Area of circle %0.2f", area)
}

Run in playground

In the program above, we check whether the radius is less than zero in line no. 10. If so we return zero for the area along with the corresponding error message. If the radius is greater than 0, then the area is calculated and nil is returned as the error in line no. 13.

In the main function, we check whether the error is not nil in line no. 19. If it’s not nil, we print the error and return, else the area of the circle is printed.

In this program the radius is less than zero and hence it will print,

Area calculation failed, radius is less than zero  

Adding more information to the error using Errorf

The above program works well but wouldn’t it be nice if we print the actual radius which caused the error. This is where the Errorf function of the fmt package comes in handy. This function formats the error according to a format specifier and returns a string as value that satisfies the error interface.

Let’s use the Errorf function and make the program better.

package main

import (  
    "fmt"
    "math"
)

func circleArea(radius float64) (float64, error) {  
    if radius < 0 {
        return 0, fmt.Errorf("Area calculation failed, radius %0.2f is less than zero", radius)
    }
    return math.Pi * radius * radius, nil
}

func main() {  
    radius := -20.0
    area, err := circleArea(radius)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("Area of circle %0.2f", area)
}

Run in playground

In the program above, the Errorf is used in line no. 10 to print the actual radius which caused the error. Running this program will output,

Area calculation failed, radius -20.00 is less than zero  

Providing more information about the error using struct type and fields

It is also possible to use struct types which implement the error interface as errors. This gives us more flexibility with error handling. In our previous example, if we want to access the radius which caused the error, the only way now is to parse the error description Area calculation failed, radius -20.00 is less than zero. This is not a proper way to do this since if the description changes, our code will break.

We will use the strategy followed by the standard library explained in the previous tutorial under the section «Converting the error to the underlying type and retrieving more information from the struct fields» and use struct fields to provide access to the radius which caused the error. We will create a struct type that implements the error interface and use its fields to provide more information about the error.

The first step would be create a struct type to represent the error. The naming convention for error types is that the name should end with the text Error. So let’s name our struct type as areaError

type areaError struct {  
    err    string
    radius float64
}

The above struct type has a field radius which stores the value of the radius responsible for the error and err field stores the actual error message.

The next step is to implement the error interface.

func (e *areaError) Error() string {  
    return fmt.Sprintf("radius %0.2f: %s", e.radius, e.err)
}

In the above snippet, we implement the Error() string method of the error interface using a pointer receiver *areaError. This method prints the radius and the error description.

Let’s complete the program by writing the main function and circleArea function.

package main

import (  
    "errors"
    "fmt"
    "math"
)

type areaError struct {  
    err    string
    radius float64
}

func (e *areaError) Error() string {  
    return fmt.Sprintf("radius %0.2f: %s", e.radius, e.err)
}

func circleArea(radius float64) (float64, error) {  
    if radius < 0 {
        return 0, &areaError{
            err:    "radius is negative",
            radius: radius,
        }
    }
    return math.Pi * radius * radius, nil
}

func main() {  
    radius := -20.0
    area, err := circleArea(radius)
    if err != nil {
        var areaError *areaError
        if errors.As(err, &areaError) {
            fmt.Printf("Area calculation failed, radius %0.2f is less than zero", areaError.radius)
            return
        }
        fmt.Println(err)
        return
    }
    fmt.Printf("Area of rectangle %0.2f", area)
}

Run in playground

In the program above, circleArea in line no. 18 is used to calculate the area of the circle. This function first checks if the radius is less than zero, if so it creates a value of type areaError using the radius responsible for the error and the corresponding error message and then returns the address of it in line no. 20 along with 0 as area. Thus we have provided more information about the error, in this case the radius which caused the error using the fields of a custom error struct.

If the radius is not negative, this function calculates and returns the area along with a nil error in line no. 25.

In line no. 30 of the main function, we are trying to find the area of a circle with radius -20. Since the radius is less than zero, an error will be returned.

We check whether the error is not nil in line no. 31 and in line no. 33 line we try to convert it to type *areaError. If the error is of type *areaError, we get the radius which caused the error in line no. 34 using areaError.radius, print a custom error message and return from the program.

If the error is not of type *areaError, we simply print the error in line no. 37 and return. If there is no error, the area will be printed in line no.40.

The program will print,

Area calculation failed, radius -20.00 is less than zero  

Now lets use the second strategy described in the previous tutorial and use methods on custom error types to provide more information about the error.

Providing more information about the error using methods on struct types

In this section we will write a program which calculates the area of a rectangle. This program will print an error if either the length or width is less than zero.

The first step would be create a struct to represent the error.

type areaError struct {  
    err    string //error description
    length float64 //length which caused the error
    width  float64 //width which caused the error
}

The above error struct type contains an error description field along with the length and width which caused the error.

Now that we have the error type, lets implement the error interface and add a couple of methods on the error type to provide more information about the error.

func (e *areaError) Error() string {  
    return e.err
}

func (e *areaError) lengthNegative() bool {  
    return e.length < 0
}

func (e *areaError) widthNegative() bool {  
    return e.width < 0
}

In the above snippet, we return the description of the error from the Error() string method. The lengthNegative() bool method returns true when the length is less than zero and widthNegative() bool method returns true when the width is less than zero. These two methods provide more information about the error, in this case they say whether the area calculation failed because of the length being negative or width being negative. Thus we have used methods on struct error types to provide more information about the error.

The next step is to write the area calculation function.

func rectArea(length, width float64) (float64, error) {  
    err := ""
    if length < 0 {
        err += "length is less than zero"
    }
    if width < 0 {
        if err == "" {
            err = "width is less than zero"
        } else {
            err += ", width is less than zero"
        }
    }
    if err != "" {
        return 0, &areaError{
            err:    err,
            length: length,
            width:  width,
        }
    }
    return length * width, nil
}

The rectArea function above checks if either the length or width is less than zero, if so it returns an error of type *areaError, else it returns the area of the rectangle with nil as error.

Let’s finish this program by creating the main function.

func main() {  
    length, width := -5.0, -9.0
    area, err := rectArea(length, width)
    if err != nil {
        var areaError *areaError
        if errors.As(err, &areaError) {
            if areaError.lengthNegative() {
                fmt.Printf("error: length %0.2f is less than zeron", areaError.length)

            }
            if areaError.widthNegative() {
                fmt.Printf("error: width %0.2f is less than zeron", areaError.width)

            }
            return
        }
        fmt.Println(err)
        return
    }
    fmt.Println("area of rect", area)
}

In the main function, we check whether the error is not nil in line no. 4. If it is not nil, we try to convert it to type *areaError. Then using the lengthNegative() and widthNegative() methods, we check whether the error is because of the fact that the length is negative or width is negative. We print the corresponding error message and return from the program. Thus we have used the methods on the error struct type to provide more information about the error.

If there is no error, the area of the rectangle will be printed.

Here is the full program for your reference.

package main

import (  
    "errors"
    "fmt"
)

type areaError struct {  
    err    string  //error description
    length float64 //length which caused the error
    width  float64 //width which caused the error
}

func (e *areaError) Error() string {  
    return e.err
}

func (e *areaError) lengthNegative() bool {  
    return e.length < 0
}

func (e *areaError) widthNegative() bool {  
    return e.width < 0
}

func rectArea(length, width float64) (float64, error) {  
    err := ""
    if length < 0 {
        err += "length is less than zero"
    }
    if width < 0 {
        if err == "" {
            err = "width is less than zero"
        } else {
            err += ", width is less than zero"
        }
    }
    if err != "" {
        return 0, &areaError{
            err:    err,
            length: length,
            width:  width,
        }
    }
    return length * width, nil
}

func main() {  
    length, width := -5.0, -9.0
    area, err := rectArea(length, width)
    if err != nil {
        var areaError *areaError
        if errors.As(err, &areaError) {
            if areaError.lengthNegative() {
                fmt.Printf("error: length %0.2f is less than zeron", areaError.length)

            }
            if areaError.widthNegative() {
                fmt.Printf("error: width %0.2f is less than zeron", areaError.width)

            }
            return
        }
        fmt.Println(err)
        return
    }
    fmt.Println("area of rect", area)
}

Run in playground

This program will print the output,

error: length -5.00 is less than zero  
error: width -9.00 is less than zero  

We have seen examples for two of the three ways described in the error handling tutorial to provide more information about the errors.

The third way using direct comparison is pretty straightforward. I would leave it as an exercise for you to figure out how to use this strategy to provide more information about our custom errors.

This brings us to an end of this tutorial.

Here is a quick recap of what we learnt in this tutorial,

  • Creating custom errors using the New function
  • Adding more information to the error using Errorf
  • Providing more information about the error using struct type and fields
  • Providing more information about the error using methods on struct types

Have a good day.

Next tutorial — Panic and Recover

yourbasic.org/golang

String-based errors

The standard library offers two out-of-the-box options.

// simple string-based error
err1 := errors.New("math: square root of negative number")

// with formatting
err2 := fmt.Errorf("math: square root of negative number %g", x)

Custom errors with data

To define a custom error type, you must satisfy the predeclared error
interface.

type error interface {
	Error() string
}

Here are two examples.

type SyntaxError struct {
	Line int
	Col  int
}

func (e *SyntaxError) Error() string {
	return fmt.Sprintf("%d:%d: syntax error", e.Line, e.Col)
}
type InternalError struct {
	Path string
}

func (e *InternalError) Error() string {
	return fmt.Sprintf("parse %v: internal error", e.Path)
}

If Foo is a function that can return a SyntaxError or an InternalError, you may handle the two cases like this.

if err := Foo(); err != nil {
	switch e := err.(type) {
	case *SyntaxError:
		// Do something interesting with e.Line and e.Col.
	case *InternalError:
		// Abort and file an issue.
	default:
		log.Println(e)
	}
}

More code examples


Go blueprints: code for com­mon tasks is a collection of handy code examples.

Share this page:  
   
   

Понравилась статья? Поделить с друзьями:
  • Как создать ошибку 404 на сайте
  • Как снять ошибку двигателя газель некст
  • Как создать ошибку 403
  • Как снять ошибку горит чек
  • Как создать отчет об ошибке miui