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.
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
Go getting started
To enable dependency tracking for your code by creating a go.mod file, run thego 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 begithub.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.
I initialized the module path by following the command
go mod init ticklint/hello
I saw it created a file with the name go.mod
with the following content
module ticklint/hello
go 1.21.5
Let’s create a file hello.go
package main
import "fmt"
func main() {
fmt.Println("Hello, My Name is Mrityunjay!")
}
- Declared
main
package. A package is a group of functions. - Imported
fmt
package, which provides many utilities functions to format String. - We implement a
main
function to print our message in the console. - Remember – A
main
function executes by default with the execution ofmain
package.
Run the code
$ go run .
Hello, My name is Mrityunjay!
run
is one of many commands provided by Go to get various things done.- Remember – To know more about go commands use
$ go help
So this is how I can run my first Go program. It was pretty straightforward.
2.1. Using external package & function
In the Go ecosystem, many developers publish Go packages with features that can be used by other Go applications.
Remember – To explore useful packages in the Go ecosystem, visit pkg.go.dev
Let’s create a program that prints a random quote by using a quote package. I ran the following commands to initiate the new Go app:
$ pwd
<some-path>/geekmj/ticklint/go-getting-started
$ mkdir mkdir 02-external-package
$ cd 02-external-package
$ go mod init ticklint/extpkg
New main.go
in 02-external-package
:
package main
import "fmt"
import "rsc.io/quote/v4"
func main() {
fmt.Println(quote.Go())
}
To add the rsc.io/quote/v4
external module:
$ 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 aname
parameter whose type isstring
. The function also returns astring
. 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:From Go Tutorialvar 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
andticklint/hellogrt
toticklint/greetings
err andticklint/hellogrterr
. - In
04-hello-greetings/go.mod
to changereplace ticklint/greetings => ../03-greetings
toreplace 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.
Change go-getting-started/06-hello-greetings-err/main.go
:
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
:
package greetings
import (
"regexp"
"testing"
)
func TestHelloName(t *testing.T) {
name := "Mrityunjay"
want := regexp.MustCompile(`\b` + name + `\b`)
msg, err := Hello("Mrityunjay")
if !want.MatchString(msg) || err != nil {
t.Fatalf(`Hello("Mrityunjay") = %q, %v, want match for %#q, nil`, msg, err, want)
}
}
func TestHelloEmpty(t *testing.T) {
msg, err := Hello("")
if msg != "" || err == nil {
t.Fatalf(`Hello("") = %q, %v, want "", error`, msg, err)
}
}
We wrote two unit tests. To run:
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
.
I am skipping the installation of the app part.
7. Summary
In this article we learned about creating modules, using modules, Slice, Map, Unit Testing, build, etc.