GetGoing/src/1-hello-world.md

10 KiB
Raw Blame History

sitename title template
Within Go Training 1: Get Going blog

This post is the first in a series I'm creating to help people learn the Go programming language. It's aimed at people who understand the high level concepts of programming, but haven't had much practical experience with it.

Like always, feedback is very welcome. Any feedback I get will be used to help make this course even better.

What is Go?

Go is a compiled programming language made by Google. It has a lot of features out of the box, including:

  • A static type system
  • Fast compile times
  • Efficient code generation
  • Parallel programming for free*
  • A strong standard library
  • Cross-compilation with ease (including webassembly)
  • and more!

* You still have to write code that can avoid race conditions, more on those later.

Why Use Go?

Go is a very easy to read and write programming language. Consider this snippet:

func Add(x int, y int) int {
  return x + y
}

This function wraps integer addition. When you call it it returns the sum of x and y.

Installing Go

Linux

Installing Go on Linux systems is a very distribution-specific thing. Please see this tutorial on DigitalOcean for more information.

macOS

  • Go to https://golang.org/dl
  • Download the .pkg file
  • Double-click on it and go through the installer process

Windows

  • Go to https://golang.org/dl
  • Download the .msi file
  • Double-click on it and go through the installer process

Next Steps

These next steps are needed to set up your shell for Go programs.

Pick a directory you want to store Go programs and downloaded source code in. This is called your GOPATH. This is usually the go folder in your home directory. If for some reason you want another folder for this, use that folder instead of $HOME/go below.

Linux/macOS

This next step is unfortunately shell-specific. To find out what shell you are using, run the following command in your terminal:

$ env | grep SHELL

The name at the path will be the shell you are using.

bash

If you are using bash, add the following lines to your .bashrc (Linux) or .bash_profile (macOS):

export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin

Then reload the configuration by closing and re-opening your terminal.

fish

If you are using fish, create a file in ~/.config/fish/conf.d/go.fish with the following lines:

set -gx GOPATH $HOME/go
set -gx PATH $PATH $GOPATH/bin
zsh

If you are using zsh, add the following lines to your .zshrc:

export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin

Windows

Follow the instructions here.

Hello, world!

Now that everything is installed, let's test it with the classic "Hello, world!" program. Create a folder in your code folder called go_learning and create a subfolder called hello. Open a file in there called hello.go in your favorite text editor and type in the following:

// Command hello is your first Go program.
package main

import "fmt"

func main() {
  fmt.Println("Hello, world!")
}

This program prints "Hello, world!" and then immediately exits. Here's each of the parts in detail:

// Command hello is your first go program.
package main                   // Every go file must be in a package. 
                               // Package main is used for creating executable files.

import "fmt"                   // Go doesn't implicitly import anything. You need to 
                               // explicitly import "fmt" for printing text to 
                               // standard output.

func main() {                  // func main is the entrypoint of the program, or 
                               // where the computer starts executing your code
  fmt.Println("Hello, world!") // This prints "Hello, world!" followed by a newline
                               // to standard output.
}                              // This ends the main function

Now to run this program run the following command:

$ go run hello.go
Hello, world!

go run compiles and runs the code for you, without creating a persistent binary file. This is a good way to run programs while you are writing them.

To create a binary, use go build:

$ go build hello.go
$ ./hello
Hello, world!

go build has the compiler create a persistent binary file and puts it in the same directory as you are running go from. Go will choose the filename of the binary based on the name of the .go file passed to it. These binaries are usually static binaries, or binaries that are safe to distribute to other computers without having to worry about linked libraries.

Go Modules

Go modules are the current state of the art of dependency and project management for programs written in Go. We will cover them in much more detail in the future, but for now create a module for your hello command with the following command:

$ go mod init your-name.localhost/hello
go: creating new go.mod: module christine-dodrill.localhost/hello

This creates a file called go.mod that tells the compiler details about your project. Again, we will cover this in more detail in the future, but just know this exists for now.

Creating Other Files

Go packages map to folders on the filesystem. This means that every .go file in the same filesystem folder must be inside the same package in order for the package to compile. There are some edge case exceptions to this rule, of course, but those will be covered later.

Adding Numbers

Create a new file called math.go with the following contents:

package main

// Add adds two numbers and returns the sum. 
func Add(x, y int) int {
  return x + y
}

This file creates a function called Add that adds the numbers it is given. This function is also documented because there's a comment immediately above it explaining what it does. We can view the documentation with the go doc command:

$ go doc -all
Command hello is your first Go program.

FUNCTIONS

func Add(x, y int) int
    Add adds two numbers and returns the sum. This helps you understand functions.

Go automatically keeps track of documentation comments and lets you query them with go doc. These comments should usually help explain why things are the way they are. That helps your future self understand what is going on there.

Dividing Numbers

Go allows functions to return multiple values. When a function returns a result but can also results in an error it is a common idiom to return two values: the result and the error.

Unlike Java and Python, Go does not have have Exceptions that get thrown. Instead, errors are regular values returned by functions.

We can model this idiom with integer division, where anything divided by zero is famously undefined.

Add the following to math.go:

// ErrDivideByZero is returned when Divide would divide by zero.
var ErrDivideByZero = errors.New("divide by zero")

// Divide performs floating-point division and returns the result. If the right
// hand side of Divide is zero, it returns ErrDivideByZero to avoid a panic.
func Divide(lhs, rhs float64) (float64, error) {
	if rhs == 0 {
		return 0, ErrDivideByZero
	}

	return lhs / rhs, nil
}

And add this to the top of math.go, just below the package line:

import "errors"

We need to import the errors package to create the ErrDivideByZero error. This error will be returned when the left-hand-side of the Divide function is zero.

Weaving it Together

Now these functions in math.go are usable from hello.go. Let's add a few numbers after the "Hello, world!" call.

var sum int = Add(2, 2)
fmt.Println("2 + 2:", sum)

This creates an integer variable called sum and sets it to the result of adding 2 and 2 together. You can also create the sum variable by doing this:

sum := Add(2, 2)

The := operator automagically declares the variable on its left hand side with the value on its right hand side.

Now run it with go run .. The . argument tells go run to use the package in the current working directory.

$ go run .
Hello, world!
2 + 2: 4

Let's add division. Add the following lines under the last fmt.Println call:

quotient, err := Divide(4, 2)
if err != nil {
  panic(err)
}
fmt.Println("4 / 2:", quotient)

And run it:

$ go run .
Hello, world!
2 + 2: 4
4 / 2: 2

Now, what would happen if you divide something by zero? Let's find out by adding the following after the last fmt.Println call:

quotient, err = Divide(4, 0)
if err != nil {
  panic(err)
}
fmt.Println("but the quotient of 4 / 0 was:", quotient)

The := operator isn't used here because that would cause a compile error. This error would happen because the last := already defined the variables and names cannot be redefined in Go.

Now let's run it:

$ go run .
Hello, world!
2 + 2: 4
4 / 2: 2
panic: divide by zero

goroutine 1 [running]:
main.main()
	/Users/christine.dodrill/Code/go_learning/hello/hello.go:20 +0x26c
exit status 2

This panics with the error "divide by zero" because of the call on line 20. Let's change that panic to a println so we can see what the quotient would be:

quotient, err = Divide(4, 0)
if err != nil {
  fmt.Println("got an error:", err)
}
fmt.Println("but the quotient of 4 / 0 was:", quotient)

And run it:

$ go run .
Hello, world!
2 + 2: 4
4 / 2: 2
got an error: divide by zero
but the quotient of 4 / 0 was: 0
```

The error was caught. This lets you handle errors with any custom logic you'd
need to write. Sometimes it could mean passing an error up to its caller,
sometimes it could mean retrying. It really depends on the context.

## Conclusion

And that about wraps it up for Lesson 1 in Go. Next we'll be covering making a
HTTP request and validating its response with icanhazip.com. Like I mentioned
before, feedback on this helps a lot.

Thanks and be well.