References

Introduction

Going to start the next lesson about errors.

The language has a built-in error interface. It looks like this,

type error interface {
    Error() string
}

When something can go wrong in a function, that function should return 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.

Also, just realised that golang has nil .

Here is the solution I wrote to a puzzle,

func sendSMSToCouple(msgToCustomer, msgToSpouse string) (int, error) {
	var costToCustomer, costForSpouse int
	var err error
	costToCustomer, err = sendSMS(msgToCustomer)
	if err != nil {
		return 0, err
	}
	costForSpouse, err = sendSMS(msgToSpouse)
	if err != nil {
		return 0, err
	}
	return costToCustomer + costForSpouse, nil
}

For some odd reason, I find it weird that the accepted pattern does not involve calling err.Error() . 🤷‍♂️

Here is some example code of a custom error struct,

type userError struct {
    name string
}
 
func (e userError) Error() string {
    return fmt.Sprintf("%v has a problem with their account", e.name)
}

and here is it’s usage,

func sendSMS(msg, userName string) error {
    if !canSendToUser(userName) {
        return userError{name: userName}
    }
    ...
}

I find the creation of the struct = userError{name: userName} to be fascinating. If this were python, to create a dictionary this way, I’d need to use quotes around the key.

One takeaway for me is that golang does not have error handling. There seems to be a convention on how to deal with them - but there is nothing the language provides to assist.

So, it is kindof a surprise to me to see that the language comes with an errors package. Like this,

var err error = errors.New("something went wrong")

I must say, I like the fact that the walrus operator saves me the trouble of defining variable types.

This is interesting - there exists a fmt.Errorf() print method that wraps an error with some additional context.

There is some stuff here about panic and defer . My takeaway is that these should be avoided unless I know what I’m doing.

The code block about defer,

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")
}

feels weird. Mostly because I do not see the defer creating a block of code around the function that will raise the panic . Black magic? 🤷‍♂️

Important thing about panic = when a function calls panic , the program crashes and prints a stack trace.

NOTE: There exists a log.Fatal - another approach to dealing with unrecoverable situations.

Looks like go has the usual continue and break keywords for loops.

There is a built-in square root function in golang ,

import ("math")
 
sqrt = math.Sqrt(x)  // returns float64