Install Go:
1GO_VERSION="1.21.0"
2
3wget https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz
4sudo rm -rf /usr/local/go
5sudo tar -C /usr/local -xzf go${GO_VERSION}.linux-amd64.tar.gz
6
7export PATH="/usr/local/go/bin:$PATH"
8
9go version
To keep Go available after reboot:
1echo 'export PATH="/usr/local/go/bin:$PATH"' >> ~/.bashrc
2source ~/.bashrc
1mkdir myapp
2cd myapp
3
4go mod init myapp
A go.mod file is created:
1myapp/
2└── go.mod
It describes the Go module and its dependencies.
Create main.go:
1package main
2
3import "fmt"
4
5func main() {
6 fmt.Println("Hello World")
7}
1go run .
2go build
This creates a binary: ./myapp
1package main
2
3import "fmt"
4
5func main() {
6 name := "Gopher"
7 age := 10
8
9 fmt.Println(name, age)
10}
Variables can also be declared explicitly:
1var name string = "Gopher"
2var age int = 10
The short syntax is commonly used inside functions:
1name := "Gopher"
Functions are declared with func:
1func hello(name string) string {
2 return "Hello " + name
3}
Use it:
1func main() {
2 message := hello("Gopher")
3 fmt.Println(message)
4}
A function can return several values:
1func divide(a, b int) (int, error) {
2 if b == 0 {
3 return 0, fmt.Errorf("division by zero")
4 }
5
6 return a / b, nil
7}
1if age >= 18 {
2 fmt.Println("adult")
3} else {
4 fmt.Println("minor")
5}
Go does not require parentheses around conditions.
Go uses for for loops:
1for i := 0; i < 5; i++ {
2 fmt.Println(i)
3}
It can also behave like a while loop:
1i := 0
2
3for i < 5 {
4 fmt.Println(i)
5 i++
6}
Slices are commonly used instead of arrays:
1names := []string{
2 "Alice",
3 "Bob",
4 "Charlie",
5}
6
7for _, name := range names {
8 fmt.Println(name)
9}
Add an element:
1names = append(names, "Dave")
Maps store key/value pairs:
1ports := map[string]int{
2 "http": 80,
3 "https": 443,
4}
5
6fmt.Println(ports["https"])
Loop over a map:
1for name, port := range ports {
2 fmt.Println(name, port)
3}
Structs group related data:
1type Server struct {
2 Name string
3 Port int
4}
Create one:
1server := Server{
2 Name: "web",
3 Port: 8080,
4}
5
6fmt.Println(server.Name)
Methods are functions attached to a type:
1type Server struct {
2 Name string
3}
4
5func (s Server) Start() {
6 fmt.Println("Starting", s.Name)
7}
Usage:
1server := Server{Name: "web"}
2server.Start()
Go usually returns errors explicitly:
1file, err := os.Open("config.yaml")
2if err != nil {
3 return err
4}
5
6defer file.Close()
A very common pattern is:
1result, err := doSomething()
2if err != nil {
3 return err
4}
Go does not use exceptions for normal error handling.
Code can be split into packages.
Example:
1myapp/
2├── main.go
3└── greeting/
4 └── greeting.go
greeting/greeting.go:
1package greeting
2
3func Hello(name string) string {
4 return "Hello " + name
5}
Use it from main.go:
1package main
2
3import (
4 "fmt"
5
6 "myapp/greeting"
7)
8
9func main() {
10 fmt.Println(greeting.Hello("Gopher"))
11}
Names beginning with an uppercase letter are exported:
1func Hello() {}
Names beginning with lowercase are private to the package:
1func hello() {}
A small application can simply use:
1myapp/
2├── go.mod
3├── go.sum
4└── main.go
As the project grows, a common structure is:
1myapp/
2├── cmd/
3│ └── myapp/
4│ └── main.go
5│
6├── internal/
7│ ├── app/
8│ ├── config/
9│ └── service/
10│
11├── go.mod
12└── go.sum
Typical responsibilities:
1cmd/
2 application entry points
3
4internal/
5 private application packages
6
7go.mod
8 module and dependency definition
9
10go.sum
11 dependency checksums
main.go should generally stay small:
1package main
2
3import "myapp/internal/app"
4
5func main() {
6 app.Run()
7}
The application logic can then live under internal/.
1go run .
2go build
3go test ./...
4go fmt ./...
5go vet ./... #Static analysis:
6go mod download
7go mod tidy
8go install .
A typical Go workflow is:
1create module
2 ↓
3write packages
4 ↓
5go fmt
6 ↓
7go test
8 ↓
9go vet
10 ↓
11go build
The main ideas to remember are:
1package groups Go code
2func defines behavior
3struct groups data
4interface describes behavior
5error represents failures
6go.mod defines the module