Error handling and Loops in Go

Go programs express errors with error
values. An Error is any type that implements the simple built-in error interface:
type error interface {
Error() string
}
When something can go wrong in a function, that function should return an error
as its last return value. Any code that calls a function that can return an error
should handle errors by testing whether the error is nil
.
Atoi :
Let's look at how the strconv.Atoi
function uses this pattern. The function signature of Atoi
is:
func Atoi(s string) (int, error)
This means Atoi
takes a string argument and returns two values: an integer and an error
. If the string can be successfully converted to an integer, Atoi
returns the integer and a nil
error. If the conversion fails, it returns zero and a non-nil error.
Here's how you would safely use Atoi
:
// Atoi converts a stringified number to an integer
i, err := strconv.Atoi("42b")
if err != nil {
fmt.Println("couldn't convert:", err)
// because "42b" isn't a valid integer, we print:
// couldn't convert: strconv.Atoi: parsing "42b": invalid syntax
// Note:
// 'parsing "42b": invalid syntax' is returned by the .Error() method
return
}
// if we get here, then the
// variable i was converted successfully
A nil
error denotes success; a non-nil error denotes failure.
Another example :
We offer a product that allows businesses to send pairs of messages to couples. It is mostly used by flower shops and movie theaters.
Complete the sendSMSToCouple
function. It should send 2 messages, first to the customer and then to the customer's spouse.
Use
sendSMS()
to send themsgToCustomer
. If an error is encountered, return0
and the error.Do the same for the
msgToSpouse
If both messages are sent successfully, return the total cost of the messages added together.
When you return a non-nil error in Go, it's conventional to return the "zero" values of all other return values.
package main
import (
"fmt"
)
func sendSMSToCouple(msgToCustomer, msgToSpouse string) (int, error) {
c, err := sendSMS(msgToCustomer)
if err != nil {
return 0, err
}
s, er := sendSMS(msgToSpouse)
if er != nil {
return c, er
}
return c + s, nil
}
// don't edit below this line
func sendSMS(message string) (int, error) {
const maxTextLen = 25
const costPerChar = 2
if len(message) > maxTextLen {
return 0, fmt.Errorf("can't send texts over %v characters", maxTextLen)
}
return costPerChar * len(message), nil
}
The Error Interface
Because errors are just interfaces, you can build your own custom types that implement the error
interface. Here's an example of a userError
struct that implements the error
interface:
type userError struct {
name string
}
func (e userError) Error() string {
return fmt.Sprintf("%v has a problem with their account", e.name)
}
It can then be used as an error:
func sendSMS(msg, userName string) error {
if !canSendToUser(userName) {
return userError{name: userName}
}
...
}
Another example :
package main
import (
"fmt"
)
type divideError struct {
dividend float64
}
func (d divideError) Error() string {
return fmt.Sprintf("can not divide %v by zero", d.dividend)
}
func divide(dividend, divisor float64) (float64, error) {
if divisor == 0 {
return 0, divideError{dividend: dividend}
}
return dividend / divisor, nil
}
The Errors Package
The Go standard library provides an "errors" package that makes it easy to deal with errors.
Every time there’s no need to create a new custom error type like above, rather u can use this package.
var err error = errors.New("something went wrong")
Another example :
package main
import (
"errors"
)
func divide(x, y float64) (float64, error) {
if y == 0 {
return 0.0, errors.New("no dividing by zero")
}
return x / y, nil
}
Panic
As we've seen, the proper way to handle errors in Go is to make use of the error interface. Pass errors up the call stack, treating them as normal values:
func enrichUser(userID string) (User, error) {
user, err := getUser(userID)
if err != nil {
// fmt.Errorf is GOATed: it wraps an error with additional context
return User{}, fmt.Errorf("failed to get user: %w", err)
}
return user, nil
}
However, there is another way to deal with errors in Go: the panic
function. When a function calls panic
, the program crashes and prints a stack trace.
As a general rule, do not use panic!
The panic
function yeets control out of the current function and up the call stack until it reaches a function that defers a recover
. If no function calls recover
, the goroutine (often the entire program) crashes.
func enrichUser(userID string) User {
user, err := getUser(userID)
if err != nil {
panic(err)
}
return user
}
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("recovered from panic:", r)
}
}()
// this panics, but the defer/recover block catches it
// a truly astonishingly bad way to handle errors
enrichUser("123")
}
Sometimes new Go developers look at panic/recover
, and think, "This is like try/catch
! I like this!" Don't be like them.
I use error values for all "normal" error handling, and if I have a truly unrecoverable error, I use log.Fatal to print a message and exit the program.
Loops in Go
The basic loop in Go is written in standard C-like syntax:
for INITIAL; CONDITION; AFTER{
// do something
}
INITIAL
is run once at the beginning of the loop and can create
variables within the scope of the loop.
CONDITION
is checked before each iteration. If the condition doesn't pass
then the loop breaks.
AFTER
is run after each iteration.
For example:
for i := 0; i < 10; i++ {
fmt.Println(i)
}
// Prints 0 through 9
ANOTHER EXAMPLE:
package main
import "fmt"
func bulkSend(numMessages int) float64 {
sum := 0.0
for i:=0; i<numMessages; i++ {
sum += (1.0 + (0.01*float64(i)))
}
return sum
}
func main(){
fmt.Printf("%v",bulkSend(3))
}
Omitting Conditions from a for Loop in Go
Loops in Go can omit sections of a for loop.
For example, the CONDITION
(middle part) can be omitted which causes the loop to run forever.
for INITIAL; ; AFTER {
// do something forever
}
ANOTHER EXAMPLE :
func maxMessages(thresh int) int {
sum := 0.0
for i:=0; ; i++ {
sum += (1.0 + 0.01*float64(i))
if sum > thresh {
return i
}
}
}
There Is No While Loop in Go
Most programming languages have a concept of a while
loop. Because Go allows for the omission of sections of a for
loop, a while
loop is just a for
loop that only has a CONDITION.
for CONDITION {
// do some stuff while CONDITION is true
}
For example:
plantHeight := 1
for plantHeight < 5 {
fmt.Println("still growing! current height:", plantHeight)
plantHeight++
}
fmt.Println("plant has grown to ", plantHeight, "inches")
Continue & Break
Whenever we want to change the control flow of a loop we can use the continue
and break
keywords.
The continue
keyword stops the current iteration of a loop and continues to the next iteration. continue
is a powerful way to use the guard clause pattern within loops.
for i := 0; i < 10; i++ {
if i % 2 == 0 {
continue
}
fmt.Println(i)
}
// 1
// 3
// 5
// 7
// 9
The break
keyword stops the current iteration of a loop and exits the loop.
for i := 0; i < 10; i++ {
if i == 5 {
break
}
fmt.Println(i)
}
// 0
// 1
// 2
// 3
// 4
Subscribe to my newsletter
Read articles from Rajesh Gurajala directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
