388 lines
10 KiB
Markdown
388 lines
10 KiB
Markdown
---
|
||
sitename: Within
|
||
title: "Go Training 1: Get Going"
|
||
template: blog
|
||
---
|
||
|
||
This post is the first in a series I'm creating to help people learn the
|
||
[Go][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.
|
||
|
||
[go]: https://golang.org
|
||
|
||
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:
|
||
|
||
```go
|
||
func Add(x int, y int) int {
|
||
return x + y
|
||
}
|
||
```
|
||
|
||
This function wraps [integer
|
||
addition](https://golang.org/ref/spec#Arithmetic_operators). 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](https://www.digitalocean.com/community/tutorials/how-to-install-go-on-ubuntu-18-04)
|
||
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:
|
||
|
||
```console
|
||
$ 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](https://github.com/golang/go/wiki/SettingGOPATH#windows).
|
||
|
||
## 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:
|
||
|
||
```go
|
||
// 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:
|
||
|
||
```go
|
||
// 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:
|
||
|
||
```console
|
||
$ 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`:
|
||
|
||
```console
|
||
$ 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](https://github.com/golang/go/wiki/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:
|
||
|
||
```console
|
||
$ 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:
|
||
|
||
```console
|
||
$ 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`:
|
||
|
||
```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:
|
||
|
||
```go
|
||
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.
|
||
|
||
```go
|
||
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:
|
||
|
||
```go
|
||
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.
|
||
|
||
```console
|
||
$ go run .
|
||
Hello, world!
|
||
2 + 2: 4
|
||
```
|
||
|
||
Let's add division. Add the following lines under the last fmt.Println call:
|
||
|
||
```go
|
||
quotient, err := Divide(4, 2)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
fmt.Println("4 / 2:", quotient)
|
||
```
|
||
|
||
And run it:
|
||
|
||
```console
|
||
$ 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:
|
||
|
||
```go
|
||
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:
|
||
|
||
```console
|
||
$ 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:
|
||
|
||
```go
|
||
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:
|
||
|
||
```console
|
||
$ 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.
|