Contents
Outline
In this blog post, I will show you how to handle errors in Golang. You can see the full source code of this blog post on the link below.
Error handling
In programming, errors can occur anywhere, anytime.
- Bug: Human error(programmer’s mistake), program malfunction
- External environment: Machine problem(out of memory), physical problem(power off)
There are two ways to handle errors. One is to make the program exits, and the other is to handle the error and continue the program.
Return error
Golang returns errors as follows so that users of the code can handle the errors.
import "os"
file, err := os.Open(filename)
file, err := os.Create(filename)
To check this, create the main.go
file and modify it like the below.
package main
import (
"bufio"
"fmt"
"os"
)
func ReadFile(filename string) (string, error) {
file, err := os.Open(filename)
if err != nil {
return "", err
}
defer file.Close()
rd := bufio.NewReader(file)
line, _ := rd.ReadString('\n')
return line, nil
}
func WriteFile(filename string, data string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
_, err = fmt.Fprintf(file, data)
return err
}
const filename string = "data.txt"
func main() {
line, err := ReadFile(filename)
if err != nil {
err = WriteFile(filename, "Hello, World!\n")
if err != nil {
fmt.Println("Failed to create file!", err)
return
}
line, err = ReadFile(filename)
if err != nil {
fmt.Println("Failed to read file!", err)
return
}
}
fmt.Println("File contents: ", line)
}
When the code is executed, you can see the following result.
# go run main.go
File contents: Hello, World!
So, when you create a function and provide it, it’s better to return errors for making the user can handle the errors.
Return custom error
You can make the custom error like the following in Golang.
fmt.Errorf(formatter string, ...interface {})
Also, you can create errors with the errors
package like the below.
import "errors"
errors.New(text string) error
To check this, modify the main.go
file like the below.
package main
import (
"errors"
"fmt"
)
func errorTest(i int) (int, error) {
switch i {
case 0:
return i, fmt.Errorf("Error: %d", i)
case 1:
return i, errors.New("Error")
default:
return i, nil
}
}
func main() {
i, err := errorTest(0)
if err != nil {
fmt.Println(err)
}
i, err = errorTest(1)
if err != nil {
fmt.Println(err)
}
i, err = errorTest(2)
if err != nil {
fmt.Println(err)
}
fmt.Println(i)
}
When you execute the code, you can see the result like the below.
# go run main.go
Error: 0
Error
2
Normally, if you want to show the specific variables in the error message, you can use the fmt.Sprintf
function. And if you want to show the simple string message, you can use the errors.New
function.
Error type
If you want to add more information to the error, you can use the error type
.
type error interface {
Error() string
}
In Golang, you can use the error type that implements the Error
method. For example, if you define the PasswordError
type like the below and implement the Error
method, you can use it for the error type.
type PasswordError struct {
RequiredLen int
Len int
}
func (e PasswordError) Error() string {
return "Password is too short."
}
To check this, modify the main.go
file like the below.
package main
import (
"fmt"
)
type PasswordError struct {
RequiredLen int
Len int
}
func (e PasswordError) Error() string {
return "Password is too short."
}
func RegisterAccount(name, password string) error {
if len(password) < 8 {
return PasswordError{RequiredLen: 8, Len: len(password)}
}
// register account logic
// ...
return nil
}
func main() {
err := RegisterAccount("john", "12345")
if err != nil {
if errInfo, ok := err.(PasswordError); ok {
fmt.Printf("%v (Required: %d Len: %d)\n", errInfo, errInfo.RequiredLen, errInfo.Len)
}
}
}
When you execute the code, you can see the result like the below.
# go run main.go
Password is too short. (Required: 8 Len: 5)
Like this, if you want to add more information to the error, you can use the error type
.
Error wrapping
In Golang, you can wrap the various errors to make one error. To wrap errors, you can use the fmt.Errorf
function.
fmt.Errorf("%w", err)
Also, you can use the Is
and As
function in the errors
package to check the error is wrapped or not.
errors.Is(err, StrError)
errors.As(err, &strError)
To check this, modify the main.go
file like the below.
package main
import (
"errors"
"fmt"
)
type StrError struct {
Msg string
}
func (e *StrError) Error() string {
return fmt.Sprintf("Message: %s", e.Msg)
}
func msgError(msg string) error {
return &StrError{Msg: msg}
}
func WrapError(msg string) error {
err := msgError(msg)
return fmt.Errorf("(Wrapping) %w", err)
}
func main() {
err1 := msgError("Error")
fmt.Println("[Normal Error]", err1)
err2 := WrapError("Test Error Message")
fmt.Println("[Wrapping Error]", err2)
var strErr *StrError
if errors.As(err2, &strErr) {
fmt.Printf("[Failed] %s\n", strErr)
}
}
When you execute the code, you can see the following result.
# go run main.go
[Normal Error] Message: Error
[Wrapping Error] (Wrapping) Message: Test Error Message
[Failed] Message: Test Error Message
Like this, you can wrap the errors to make one error and handle it in Golang.
Panic
In Golang, you can use the panic
to exit the program with the error. Panic makes the program exit quickly, so makes the programmers recognize it and handle it faster.
To check this, modify the main.go
file like the below.
package main
import "fmt"
func divide(a, b int) {
if b == 0 {
panic("divide by zero")
}
fmt.Printf("%d / %d = %d\n", a, b, a/b)
}
func main() {
divide(4, 2)
divide(4, 0)
divide(4, 3)
}
Whe the code is executed, you can see the result like the below.
# go run main.go
4 / 2 = 2
panic: divide by zero
goroutine 1 [running]:
main.divide(0x4, 0x2)
/study-golang/error-handling/5.panic/main.go:7 +0x10b
main.main()
/study-golang/error-handling/5.panic/main.go:14 +0x31
exit status 2
The panic occurs in the second divide
function call. So the program exits with the error, and the third divide
function call is not executed.
You can pass any types to the panic function like the below.
func panic(v interface{})
So, you can use the panic as follows.
panic(42)
panic("error")
panic(fmt.Errorf("error num: %d", 42))
panic(SomeType{SomeData})
Panic propagation and recovery
When we develop the programs, it’s important to find the issue fastly. So, when we use the panic, it helps us to find the issue quickly and fix it.
However, when the program is released, it’s important to keep the service continuously running. When people use the program and the program is crashed fastly, no one wants to use the program.
So, Golang provides the recover
to recover the panic.
func recover() interface {}
We can recover the panic with the recover
and defer
in the funciton. To check this, modify the main.go
file like the below.
package main
import (
"fmt"
)
func firstCall() {
fmt.Println("(2) firstCall function start")
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in firstCall", r)
}
}()
group()
fmt.Println("(2) firstCall function end")
}
func group() {
fmt.Println("(3) group function start")
fmt.Printf("4/2 = %d\n", divide(4, 2))
fmt.Printf("4/0 = %d\n", divide(4, 0))
fmt.Println("(3) group function end")
}
func divide(a, b int) int {
if b == 0 {
panic("divide by zero")
}
return a / b
}
func main() {
fmt.Println("(1) main function start")
firstCall()
fmt.Println("(1) main function end")
}
When you execute the code, you can see the result like the below.
# go run main.go
(1) main function start
(2) firstCall function start
(3) group function start
4/2 = 2
Recovered in firstCall divide by zero
(1) main function end
As you see, the recover
is used in the firstCall
function to recover the panic. And the panic ourrs in the divide
function. So, the panic is propagated to divide() > group() > firstCall() > main()
. In here, we recovered the panic in the firstCall
function, so the panic is not propagated to the main()
.
When you’re developing, I recommend you not to use the recover. If you use the recover, the program won’t exit, so it’s difficult to find the error or recognize it.
When the program is released, we don’t have any choice. We should use the recover to keep the service running. However, the same problem occurs. The recover makes us hard to find the issues, so making the email notificaion or saving the error to the database may be a good solution for this.
Golang does not support SHE
Golang doesn’t support SHE
(Structured Exception Handling).
try {
...
} catch (Exception ex) {
...
} finally {
...
}
Other programmin languages supoort the try-catch
statement to handle errors, but Golang doesn’t support it. SHE has some issues as follows.
- Performance issue: the structure is cheked and executed in the normal status, so it makes some performance issue.
- It is difficult to recognize errors. For this reason, some errors are not handled and may be neglected.
Completed
Done! we’ve seen how to define errors and how to handle them in Goalng. We’ve seen how to make an error with the program running and how to make the program exit with an error by panic. Also we’ve seen how to use the recover to recover the error.
The error is very important role in the programming. So, if you use the function which returns an error, it’s better to handle it instead of using the blank identifier(_
). Also, using the recover
to recover the panic for keeping the program running is a good solution, but it’s better to send a notification or save the error to the database for recoginizing the error.
Was my blog helpful? Please leave a comment at the bottom. it will be a great help to me!
App promotion
Deku
.Deku
created the applications with Flutter.If you have interested, please try to download them for free.