Learning Golang

Francisco Schulz
Towards Dev
Published in
8 min readMar 24, 2021

--

This is my summary of the Golang basics. There is always a block of code and below it, some bullet-points describing what it does + notable things that might be unique to Golang or just interesting to know. I go from general to more specific/complex here, therefore, it might be useful to read through everything top to bottom if you are completely new to Golang, like me.

If the mediocre code highlights bother you here on Medium, you can read this article directly on my Notion.so page (also free)

Basics

// define a file to be executable
package main
// import packages
import "fmt"
// declare a function
func main () {
fmt.Println("Hello World!")
}
  • In Go, programs that start with the package mainline will automatically execute the main function at the end of the script. We do not have to call it explicitly.
  • Scripts that do not start with package main are considered libraries, accumulations of code, and can be used in other programs themselves.
// do multiple imports like his
import "fmt"
import "time"
// OR
import (
"fmt"
t "time" // the 't' will be the shorthand for the package
)
# run files without compiling
go run main.go
# compile go scirpts
go build main.go
# execute binary
./main
  • Building a go script compiles the code into a binary file and saved it in the working directory
  • The binary can then be executed as shown
# search the go docs like this
go doc fmt
# search for specific function
go doc time.Now
  • Docs can be searched for on the web or easily accessed via the CLI

Variable Declarations

package mainimport (
"fmt"
)
func main() {
// static type declaration
var firstName string
var lastName string
var age int

// assign values to the variables
age = 12
firstName = "Potato"
lastName = "Joe"

// one line declaration and assignment
var isCool bool = True
// string concatenation + type inference
var name = firstName + " " + lastName
// also possible - using the walrus operator:
// name := firstName + " " + lastName
// unless variables are used program will not run/compile
// fmt.Println(name, "is", age, "years old and very cool:", isCool)
}
  • Here variables were declared that can change their values in the process of a program. Static type declarations make the code more verbose but also more robust.
  • Type declarations and variable assignments can also be done in one line.
  • One can explicitly state the size of an integer or a float like so: int8 int16 int32 int64 float32 float64. Also, there is uint, it allows for larger positive values than their non-u counterparts (more info here). However, it's recommended to let Golang take care of that unless you have explicit reasons.
  • The variables in the code above were not used. They have only been declared. This will keep the Go-Script from running or being compiled. One will have to remove the unused variables or fix potential typos. A tough stance from the makers of Go that requires more attention to detail, but also saves memory and discovers potential bugs early.
const fullName = "Potato Joe Jr. II"
  • Constant named values, like variables, can be set without a specific type declaration and cannot change their values after assignment.
  • Also, notice that in Golang we use mixedCase for naming our variables and constants.
var coffeeLifeTotal int
coffeeDayCount := 7
fmt.Println(coffeeLifeTotal + coffeeDayCount) // prints 7
  • coffeeLifeTotal has not been assigned a value, still, we can use it in the print statement.
  • This is possible because Go assigns new variables a standard value:
  • int -> 0
  • float -> 0.0
  • bool -> false
  • string -> ""
// multi-assignment
var name, country, city string
name, country, city = "Potato Joe", "Federal Union of Potatoes", "Potato Town"
// or quick and dirty
coffeeName, coffeeCountry, awesomenessFactor := "Yirgacheffe", "Ethiopia", 9001
  • Like in Python, we can assign multiple variables in one line. This also works if we have different variable types. Using the walrus operator (:=) type inference is also on the menu.
// if-statements
var coffeeCount uint
if (coffeeCount == 0) { // placing condition in () is optional
fmt.Println("You haven't had coffee yet?! Outrageous!")
} else if (coffeeCount <= 3) {
fmt.Println("Keep going, don't give up!")
} else {
fmt.Println("Good on ya, mate! Keep it up. Proud of you.")
}
  • Conditions write like JavaScript in Go, just without the ; at the end of a line - otherwise, there is nothing unexpected. One thing to be aware of is the logical operators && and || and ! - denoting AND, OR, and NOT respectively.
  • To build more elaborate condition statements check out the operators that are supported in Go here.
// print addresses
fmt.Println(&coffeeName) // -> e.g. 0xc0000101e0
// store the address of a variable in a second variable aka. pointer
var pointerVar *string // * - defines a pointer
// string - tells Golang what type of variable
// is at the address
pointerVar = &coffeeName // alt.: pointerVar := &coffeeName// change variable value in-memory at the gven address
// aka. deference the variable
*pointerVar = "NewAndUndiscovered"
fmt.Println(coffeeName) // -> "NewAndUndiscovered"
  • Golang is a “pass-by-value” language that results in different scopes for our variables. It matters if they are declared locally, i.e. in a function, or globally.
  • Each time we create a variable the computer assigns it some space in memory. With &varName we can check which address the variable has in-memory, which is returned to us as a hexadecimal number starting with 0x
  • We can use pointers to change the data at that specific location. This is called dereferencing.

Data Structures

// slice
var coffeeVarieties []string = []string{"Bourbon", "Heirloom", "Geisha"}
// add an element
coffeeVarienties = append(coffeeVarieties, "SL-28")
// using make()
allZeroSlice := make([]int, 3) // -> [0 0 0]
// multi-dim slice
multiDimSlice := [][]string{
{"a", "b"},
{"c", "d"}, // keep last comma
} // -> [[a b] [c d]]
// check length and capacity of a slice
fmt.Println(len(coffeeVarieties), cap(coffeeVarieties)) // -> 4, 4
// arrays
grindSize := [10]int{1, 2, 3, 4, 5, 6, 7} // -> [1 2 3 4 5 6 7 0 0 0]
// multi-dim arrays
multiDimArr := [2][3]int{
{1, 2, 3},
{4, 5, 6},
} // -> [[1 2 3] [4 5 6]]
// check length and capacity of am array
fmt.Println(len(grindSize), cap(grindSize)) // -> 10, 10
// access items over braket notation
fmt.Println(coffeeVarieties[2], multiDimArr[1][:2]) // -> Geisha [4 5]
// maps
coffeeRanking := map[int]string{
1: "Costa Rica",
2: "Ethiopia",
3: "Kenia",
}
  • Slices aren’t fixed in size and, therefore, provide quite a bit of flexibility. Also, they can be created from arrays.
  • Arrays are used for the consecutive storage of elements with the same data type.
  • Maps are like dictionaries in Python and store unique key-value pairs.

Conditionals

coffeeCount := 10
coffeeMugs := 3
if ratio := coffeeCount / coffeeMugs; ratio < 3 {
fmt.Println("Dude, don't use so many mugs!")
}
  • We can use shorthand notations to declare a variable inside an if-statement
  • The variable will be scoped towards the statement which declares it and therefore only accessible inside it
// simplify superlong conditionals with switch-statements
switch coffeeCountry := "Ethiopia"; coffeeChoice {
case "Ethiopia":
fmt.Println("Prepare for berrylike fruitiness.")
case "Kenia":
fmt.Println("Mmmh, complex citrusy tastes.")
case "Costa Rica":
fmt.Println("High altitudes develop uncanny results!")
case "Columbia":
fmt.Println("Increadiby diverse flavor range, often flowery or peachy notes.")
default:
fmt.Println("Sorry, we don't do that.")
}
  • Switch statements are an alternative to some lengthy if-else statements. Makes the code less verbose. I’ve used a short notation in the same way as in the if-statement before.

Loops

// for-loops// conditional
for i:=0; i<3; i++ {
fmt.Println("I love coffee!")
}
// alternatively
for i<3 {
fmt.Println("I love coffee!")
i += 1
}
// infinite
for {
fmt.Println("Infinite Coffee!")
}
// iterate over a slice, array or map
for i, v := range coffeeVarieties {
fmt.Println(i, v)
}
  • In Go you will see for-loops similar to what you might encounter in Java: for initialization; condition; postcondition {...}
  • You can end infinite for-loops by using the break keyword.
  • You can use the continue keyword to skip to the next iteration in a for-loop.
  • In Golang there is no while loop. Instead, we use the plain infinite for-loop or variations of it.

Functions

// functions
package main
import "fmt"// Define function
func drinkCoffee(strTime string) string { // define typfe of returned variable
message := "It's " + strTime + ". Time to get woke!" // only accessible in function
defer fmt.Println("To the coffee machine!") // runs every time the function terminates
return message
}
func main() {
// Call funciton
// and assign return value to new variable
msg := drinkCoffee("0900")
fmt.Println(msg) // -> "It's 0900. Time to get woke!"
}
  • As one might expect, the code inside a function will not run until the function is called.
  • Variables that are only defined inside a function (local scope) are out of scope for the rest of the program.
  • We can return multiple variables in a function by adding them to the return statement and defining their types in the headline of the function, i.e. func myFunc() (int, string) {...}
  • With defer we can run some code every time a function finishes.

Structs

// structs
package main
import "fmt"// Define struct
type Coffee struct {
name string
country string
variety string
}
func main() {
// use struct to create a new variable
var yirga = new(Coffee)
yirga.name = "Yirgachaffe"
yirga.country = "Ethiopia"
yirga.variety = "Heirloom"
fmt.Println(yirga) // -> &{Yirgachaffe Ethiopia Heirloom}
esmeralda := Coffee{
name: "Finca Esmeralda",
country: "Costa Rica",
variety: "Geisha",
}
}
  • Structs are similar to classes in Python. It’s a way of structuring data.
  • We can access elements within a struct over the dot-notation. i.e. yirga.name
  • We can also create nested structs, i.e. by using structs as type-hints.

The fmt package

// auto-spaced printing with linebreaks
fmt.Println("Hi", "I'm", "Potato Joe")
// >>> "Hi I'm Potato Joe\\n"
// raw-input printing
fmt.Print("Hi", "I'm", "Potato Joe")
// >>> "HiI'mPotato Joe"
// print formatting with placeholders
var insert_this string = "Potato"
fmt.Printf("Hi I'm %v Joe", insert_this)
// >>> "Hi I'm Potato Joe"
  • Go lets us use placeholders in our strings like in the example above. These placeholders are called verbs and are marked by a % followed by a letter. The letter dictates what the placeholder will expect type-wise or how a value will be displayed.
  • In our case v in %v doesn't mean "verb" but "value". You can check out all the formatting placeholder options here.
// concatenate strings but don't print anything to the console
some_string := fmt.Sprintln("Hi", "I'm", "Potato Joe")
  • instead of concatenating strings with the + operator, you can use the fmt.Sprint() functions.
// get user input 
var response string
fmt.Scan(&response)
  • The fmt.Scan() function will ask you for input in the CLI and then assign your input to the variable given as the function parameter.

Resources

GoLang Docs — GoLang Tutorials

The Golang Homepage

Learn to Code — for Free | Codecademy

--

--