The first step to get started with Go Lang is to install it. Installation is pretty straightforward. I liked the fact that they took care of multiple versions diligently by providing you the simple abilities.
Table of contents
1. Go Installation and multiple versions
For Mac, they provide an installer package. More detail is given on the installation page.
Once I installed the Go Lang I tried the following command to know the version.
$ go version
go version go1.21.5 darwin/arm64
For testing multiple version scenarios I installed an earlier version. I used the following commands
$ go install golang.org/dl/go1.20.12@latest
$ go1.20.12 download
Somehow while executing “go1.20.12” download gave an error saying “command not found”. To fix this error I have to put the export PATH=$PATH:$HOME/go/bin in .zshrc file.
I executed the following command against the freshly downloaded-go version
$ go1.20.12 version
go version go1.20.12 linux/amd64
Typically when we install multiple versions we want to use a specific version in a project by default. To do so we can use the following command:
$ alias go="go1.20.12"
$ go version
go version go1.20.12 darwin/arm64
The above approach doesn’t look solid and a better way is to use Go Version Manager. I didn’t try it but it’s very similar to SDKMan etc.
2. First Go Program
I created a folder 01-hello under /ticklint/go-getting-started/ to home the first code. Before we start writing code we need to enable dependency tracking for our code. As per go doc
When your code imports packages contained in other modules, you manage those dependencies through your code’s own module. That module is defined by a go.mod file that tracks the modules that provide those packages. That go.mod file stays with your code, including in your source code repository
To enable dependency tracking for your code by creating a go.mod file, run the go mod init command, giving it the name of the module your code will be in. The name is the module’s module path.
In actual development, the module path will typically be the repository location where your source code will be kept. For example, the module path might be github.com/mymodule. If you plan to publish your module for others to use, the module path must be a location from which Go tools can download your module. For more about naming a module with a module path, see Managing dependencies.
$ go get rsc.io/quote/v4
go: downloading rsc.io/quote/v4 v4.0.1
....
....
go: added rsc.io/sampler v1.3.0
Run the program:
$ go run .
Don't communicate by sharing memory, share memory by communicating.
Well, we see we are printing a better message now using an external package.
Remember:
Only one package name is allowed under one directory. Also only one Go file with the main function.
2. Creating a module and using it in other modules
Go code is grouped into packages, and packages are grouped into modules. Each module defines its dependencies to run its code, Go version, and other depending modules. Modules also have evolving versions as new features or bug fixes are added.
We will create two Go apps:
Module with the package and function to display a greeting.
Use the function to print a greeting from the earlier module.
I created a new go app directory and initiated the module:
$ pwd
<some-path>/geekmj/ticklint/go-getting-started
$ mkdir 03-greetings
$ cd 03-greetings
$ go mod init ticklint/greetings
go: creating new go.mod: module ticklint/greetings
I created a new file greetings.go
package greetings
import "fmt"
// Hello returns a greeting for the named person.
func Hello(name string) string {
// Return a greeting that embeds the name in a message.
message := fmt.Sprintf("Hi, %v. Welcome!", name)
return message
}
Remember – This is the first time I created a function and it’s mostly self-explanatory except the variable type is after the name, which is different if compared with Java or Python.
Hello function takes a name parameter whose type is string. The function also returns a string. In Go, a function whose name starts with a capital letter can be called by a function not in the same package. This is known in Go as an exported name. For more about exported names, see Exported names in the Go tour.
In Go, the := operator is a shortcut for declaring and initializing a variable in one line (Go uses the value on the right to determine the variable’s type). Taking the long way, you might have written this as:
var message string
message = fmt.Sprintf("Hi, %v. Welcome!", name)
Now I am creating another Go app in a new directory.
$ cd ..
$ pwd
/Volumes/D2/github/geekmj/ticklint/go-getting-started
$ mkdir 04-hello-greetings
$ cd 04-hello-greetings
$ go mod init ticklint/hellogrt
go: creating new go.mod: module ticklint/hellogrt
I am creating main.go:
package main
import (
"fmt"
"ticklint/greetings"
)
func main() {
// Get a greeting message and print it.
message := greetings.Hello("Mrityunjay")
fmt.Println(message)
}
Importing ticklint/greetings (the package contained in the module I created earlier) gives access to the Hello function.
The module here is local (Not published yet) hence we need to tweak our module dependencies for it.
In go.mod
module ticklint/hellogrt
go 1.21.5
replace ticklint/greetings => ../03-greetings
replace ticklint/greetings => ../03-greetings pointing to the local directory of `ticklint/greetings` module. Once published (available via download) I don’t need it.
I ran the following command to update the dependencies:
$ go mod tidy
$ tail -f go.mod
module ticklint/hellogrt
go 1.21.5
replace ticklint/greetings => ../03-greetings
require ticklint/greetings v0.0.0-00010101000000-000000000000
v0.0.0-00010101000000-000000000000 is a pseudo-version number — a generated number used in place of a semantic version number (non-existent for the module as of now).
Running the app:
$ go run .
Hi, Mrityunjay. Welcome!
So I can run an app that uses another module that I created. 😀
3. Handling Errors
In our earlier greetings.Hello function, our program didn’t handle the empty name edge case. We will introduce that error handling.
I copied the 03-greetings and 04-hello-greetings as 05-greetings-err and 06-hello-greetings-err respectively. I made the following changes:
In module name ticklint/greetings and ticklint/hellogrt to ticklint/greetingserr and ticklint/hellogrterr.
In 04-hello-greetings/go.mod to change replace ticklint/greetings => ../03-greetings to replace ticklint/greetingserr => ../05-greetings-err.
In 05-greetings-err/greetings.go
package greetings
import (
"errors"
"fmt"
)
// Hello returns a greeting for the named person.
func Hello(name string) (string, error) {
if name == "" {
return "", errors.New("Empty name")
}
// Return a greeting that embeds the name in a message.
message := fmt.Sprintf("Hi, %v. Welcome!", name)
return message, nil
}
Now Hello function returns two values: a string (message) and an error (error type from errors package).
In 06-hello-greetings-err/main.go
package main
import (
"fmt"
"log"
"ticklint/greetingserr"
)
func main() {
log.SetPrefix("greetingsErr: ")
log.SetFlags(0)
// Get a greeting message and print it.
message, err := greetings.Hello("")
if err != nil {
log.Fatal(err)
}
fmt.Println(message)
}
Now in the main function, we added a log package to print log info. We changed the code to handle two values returned by greetings.Hello function and log the error in case the error is not nil.
Running the app:
$ go run .
greetingsErr: Empty name
exit status 1
So we are done with handling our edge case. 😃
4. List and Map
We will add support for getting greetings from multiple people in one go. In coding, in a function, we will pass multiple names as an array and return a map with the key as the name and value as its greetings!
We will enhance go-getting-started/05-greetings-err/greetings.go:
package greetings
import (
"errors"
"fmt"
"math/rand"
)
// Hello returns a greeting for the named person.
func Hello(name string) (string, error) {
if name == "" {
return "", errors.New("Empty name")
}
// Return a greeting that embeds the name in a randomly selected message format
message := fmt.Sprintf(randomFormat(), name)
return message, nil
}
// Slice of names as arg
// Retur map with name, greeting key and value
func Hellos(names []string) (map[string]string, error) {
// Initialize map
messages := make(map[string]string)
// _ is blank identifier
for _, name := range names {
message, err := Hello(name)
if err != nil {
return nil, err
}
messages[name] = message
}
return messages, nil
}
func randomFormat() string {
formats := []string{
"Hi, %v. Welcome!",
"Great to see you, %v!",
"Hail, %v! Well met!",
}
return formats[rand.Intn(len(formats))]
}
Remember –
In Go, arrays are not flexible and are not meant to be associated with dynamic memory allocation. Slices, on the other hand, are abstractions built on top of these array types and are flexible and dynamic. This is the key reason why slices are more often used in Go.
In Go, you initialize a map with the following syntax: make(map[key-type]value-type). More on the map.
_ (underscore) is a blank identifier. As looping on slice return index of current item and item. We don’t need an index hence we can use _.
We used rand.Intn for randomly selecting a number between 0 to n.
package main
import (
"fmt"
"log"
"ticklint/greetingserr"
)
func main() {
log.SetPrefix("greetingsErr: ")
log.SetFlags(0)
// Get a greeting messages and print it.
names := []string{"Ram", "Shyam", "John"}
messages, err := greetings.Hellos(names)
if err != nil {
log.Fatal(err)
}
fmt.Println(messages)
}
Run the app:
$ go run .
map[John:Hail, John! Well met! Ram:Hi, Ram. Welcome! Shyam:Hail, Shyam! Well met!]
$ go run .
map[John:Hi, John. Welcome! Ram:Hi, Ram. Welcome! Shyam:Great to see you, Shyam!]
So we can see the output is a map with different messages for each name.
5. Unit test
Create a new test file go-getting-started/05-greetings-err/greetings_test.go:
05-greetings-err $ go test
PASS
ok ticklint/greetingserr 0.006s
6. Compile & build
06-hello-greetings-err $ go build
06-hello-greetings-err $ ./hellogrterr
map[John:Great to see you, John! Ram:Great to see you, Ram! Shyam:Great to see you, Shyam!]
06-hello-greetings-err $ ./hellogrterr
map[John:Great to see you, John! Ram:Great to see you, Ram! Shyam:Hail, Shyam! Well met!]
Remember – go build created an executable file for the app in the current directory. The app name is the same as the module name declared in go.mod.