Getting Started with Go Language

Getting started article for Go programming language includes installation, first program, modules, map, slice, unit test and build.

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

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 getting started

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 of main 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:

  1. 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:

  1. Module with the package and function to display a greeting.
  2. 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)
From Go Tutorial

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:

  1. In module name ticklint/greetings and ticklint/hellogrt to ticklint/greetingserr and ticklint/hellogrterr.
  2. 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

  1. 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.
  2. In Go, you initialize a map with the following syntax: make(map[key-type]value-type). More on the map.
  3. _ (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 _.
  4. 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!]

Remembergo 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.

JOIN OUR NEWSLETTER
And get notified everytime we publish a new blog post.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top