Understand Concepts in Golang

Nilesh Kesar
Revel Systems Engineering Blog
7 min readJun 6, 2022

This guide will teach you some of the most common things you will use in Go, from Variable Declaration to Goroutines. At the end of this guide, there is a challenge to try and make a small Go app that will use everything taught in this guide. There will also be a link to the source code at the end showing a solution to the challenge question.

Prerequisites

  1. You have Go installed
  2. You know how to generate a go.mod file
  3. You know how to run a go project

If you don’t know how to do one or any of these follow this guide https://medium.com/@nilesh.kesar/writing-your-first-golang-application-12dd6ac1e15

Getting Started

Create an empty folder and open up the newly created folder in your IDE, once the IDE is opened create a file called main.go. Starting on line one type the following.

package mainfunc main() {}

Now open up your terminal and navigate into the folder you created earlier, now type go mod init UnderstandingGoConcepts this will create your go mod file and you're ready to begin.

Variable Declaration

Now that you have experience printing to the screen try using one or all of the following ways to declare, assign and use a variable.

Declare, Assign, and use

package mainimport (
"fmt"
)
func main() {
var str string
str = "Hello World!!"
fmt.Println(str)
}

Initialize and use

package mainimport (
"fmt"
)
func main() {
var str string = "Hello World!!"
fmt.Println(str)
}

Short Hand Initialize and use

package mainimport (
"fmt"
)
func main() {
str := "Hello World!!"
fmt.Println(str)
}

Debugging with Delve

Delve is one of the recommended Go Debuggers to install you can follow the steps Delve Installation Steps

We are going to be working off the hello world project from above but will modify it a little bit.

package mainimport (
"fmt"
"runtime"
)
func main() {
str := "Hello World!!"
name := "John Doe"
runtime.Breakpoint() fmt.Println(str)
fmt.Println(name)
}

runtime.Breakpoint() is how you can set the delve debugger breakpoint, this will throw an error if left in when running the Go Project normally, but we are going to run the project with Delve in debug mode, this will hit the breakpoint and stop. There are two ways you can run your code to debug it, you can run the built executable Go project which you would have to rebuild anytime you make a change to the source code or you can run the debugger which will compile the Go code but will not produce an executable. For now, we will go with the second where we compile the Go code but don’t produce an executable. In your terminal window type dlv debug this will start the delve debug session in which you will see (dlv) with this, you can type c and press enter this will continue till it reaches the end of your program or a breakpoint in our case it will be a breakpoint. Now you can type print str or print name and you will see the value stored in them. These breakpoints also can work on goroutines and channels (which will be covered in the next lesson)

Once you finished your debugging session you can either continue pressing c or you can press q to quit the debugging session.

Packages

Now let's talk about packages, in Go a package is how you can separate different pieces of code into their own file and folder. Each folder in Go must contain only one package and must be unique to the project. An Example of this is let's say you have the directory tree below, the main package would be the root directory, then you would have another package named enums in the enum folder and another package named structs in the structs folder. We will show a full example in the next section Custom Types and Data Structures

| main.go
| go.mod
| --> types
| --> enums
| --> enumTest.go
| --> structs
| --> structTest.go

Custom Types and Data Structures

Let's make the file structure from above in our Current project

// enumTest.gopackage enumstype enumTest int64const (
Testing enumTest = iota // 0
Player enumTest = iota // 1
)

To explain what's going on iota in Go is a way you can set up enums so that the list of const will use the next unsigned integer so Testing will be 0 and Player will be 1.

// structTest.gopackage Structstype StructTest struct {
Test string
JSONString string `json:"abc"`
privateInteger int
}

To explain what's going on you here, Structs are like Classes, Test is a public member variable of type string. JSONString is a public member variable of type string that when marshaling and unmarshalling will map JSONString to the JSON value abc. privateInteger is a private member variable of type int. In Go, you define private and public by the first letter capitalization.

Goroutines

In Go, there is a concept Goroutines, essentially they are lightweight threads. They are highly performant and used to do things in parallel throughout your Go app. To better understand these concepts let’s update our hello world application to better show what Goroutines can really do. You can run a Goroutine on any function, with that said you can also make an anonymous goroutine function below there are examples showing them off.

package mainimport (
"fmt"
"time"
)
func printFunc(name string) {
for i := 1; i < 4; i++ {
fmt.Println(name + ": ", i)
}
}
func main() {
str := "Hello World!!"
fmt.Println(str)
fmt.Println("Showing off GoRoutines")
fmt.Println()
fmt.Println()
// running go routine using existing function
go printFunc("GoRoutine 1")
// running function in main thread
printFunc("Main thread")
// running anonymous go routine
go func() {
fmt.Println("Anonymous function GoRoutine")
}()
// Sleep is needed so main process doesn't exit until all is done
time.Sleep(time.Second * 5)
fmt.Println("Done")
}

As you can see the goroutine doesn’t run in sequence and other goroutines can run in between them.

Channels

In Go, there is a concept called Channels, it’s used to pass data between Goroutines. The way channels work is they have a buffer, and the buffer holds onto the value until it’s read from, and until it’s read from more data can not enter the buffer, for every write to the buffer there must be a read from the buffer else you can enter a deadlock. In the example below we are going to send all of our messages to a standard channel to print out to the console, this will ensure that only one process is writing out, were pretending the console is a file where we only want one thing written to the file at a time.

To Write to a Channel you do <channel_name> <- <what you want written>

To Read from a Channel you do <new_var <- <channel_name>

package mainimport (
"fmt"
"time"
)
// create global channel
var
messages chan string = make(chan string)
func handleMessage() {
var msg string
for {
// read string from buffer and clear buffer
msg = <-messages

if msg == "exit" {
fmt.Println("Ending Channel reads")
break
}
fmt.Println(msg)
}
}
func printFunc(name string) {
for i := 1; i < 4; i++ {
// write interpolated string to the buffer
messages
<- fmt.Sprintf("%s%s%d", name, ": ", i)
}
}
func main() {
// reading from channel should be run in seperate thread so its
// not blocking main thread
go handleMessage()

str := "Hello World!!"

// write a varaible string to the buffer
messages <- str
// write hardcoded string to the buffer
messages <- "Showing off GoRoutines\n\n"
printFunc("printFunc - GoRoutine")

printFunc("Main thread")

go func() {
messages <- "Anonymous function GoRoutine"
}()
time.Sleep(time.Second * 1)
messages <- "Done"
time.Sleep(time.Second * 1)
}

Put it all together (Challenge)

Now let's put all that together, we are going to be creating a little game called echo, in this game whenever someone types echo followed by a word or phrase we will repeat that phrase. An example of some output would be

> echo hello
hello
> echo hi
hi
> hello
> echo hello
hello

Here is the stuff you need to know to make sure you have a valid solution:

  1. You must use go routines, channels, structs, and enums in your solution.
  2. A single Goroutine is the only thing allowed to print to the screen anything wanting to print to the screen must send the data to that single Goroutine
  3. You should handle the following three scenarios: when a user begins their input with echo the app should repeat the user input when a user's input does not begin with echo, the app should not print anything and wait for the user to try inputting again, and lastly if the user types exit by itself, the application must end

Put it all together (Completed Code)

The completed code lives in the link below, note you could complete this any way you wish, but you must use go routines, channels, structs, and enums in your solution.
https://gitlab.com/NKesar_RS/understanding-go-concepts

Credits:

Gopher Pictures: https://github.com/MariaLetta/free-gophers-pack

--

--