Misc Golang Notes¶
Golang is a popular language for platform engineering.
Writing CLI tools, web services, middleware, APIs, etc., are common use cases.
Table of Contents¶
- Printing Lines
- The Defer Keyword
- File Structure for
main.go
- Zero Values (Zero Initialization)
- Map Declaration and Initialization
- Channels
- Goroutines
- Import Statement
- Pain Point
- Creating a New Project in Go
- Misc
Printing Lines¶
println
: Prints to stderrfmt.Println
: Prints to stdout
The Defer Keyword¶
The defer
keyword will run whatever comes after it at the end of the function.
If defer fmt.Println("bye")
is called in func main()
, it will print bye
when the
program exits.
package main
import "fmt"
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
hello world
File Structure for main.go
¶
Most common practice is to make a cmd
directory, and then another
subdirectory named after the current project, i.e., cmd/my-project
, and
put your main.go
file in there.
"n to m" is a "many to many" mapping
Zero Values (Zero Initialization)¶
Go supports zero-initialized variables by default.
Any time a variable is declared, it's given a zero-value by default.
The zero value given depends on the type of the variable.
The zero value for a type is the value that is assigned to a variable of that type when it is declared.
var some_number int
fmt.Println(some_number) // 0
some_number
variable is declared, but not initialized with a value.Go automatically assigns it the zero-value of its type.
In this case, it's an
int
, so the zero-value is 0
.
Type | Zero Value |
---|---|
string |
An empty string "" |
int |
0 |
float64 |
0.0 |
struct |
A struct with all fields set to their zero value. |
array |
An array with all elements set to their zero value. |
pointer |
nil |
slice |
nil . A nil slice has a length and capacity of 0 and no underlying array. |
map |
nil . A nil map has no keys nor can keys be added. |
channel |
nil |
interface |
nil , indicating no value and no concrete type. |
bool |
false |
function |
nil , indicating a function with no definition. |
Map Declaration and Initialization¶
m := map[int]int{1: 2, 3: 4}
m
: Declares a variable m
.
* map[int]int
: Declares a map with both keys and values of type int
.
* {1: 2, 3: 4}
: Initializes the map with two key-value pairs: 1
maps to 2
and 3
maps to 4
.
Looping Over a Map¶
for i, v := range m {
for
: Starts a loop.
* i, v :=
: Declares two variables, i
(the key from the map) and v
(the corresponding value).
* range m
: range
is used to iterate over elements in a variety of data structures.* Here, it iterates over the map
m
, returning each key-value pair.
Channels¶
Channel Creation¶
c, out := make(chan int), make(chan int)
c, out
: This declares two variables, c
and out
.* Both variables are channels that can transport integers (
int
).
* :=
: The short variable declaration operator.* It declares new variables and assigns them with the values on the right. *
make(chan int)
: This creates a new channel for transporting int
values.*
make
is a built-in function that initializes slices, maps, and channels.* Here, it's used to create two channels:
c
for synchronization and out
for
outputting the results.
Closing a Channel¶
close(c)
close(c)
: Closes the channel c
.* Closing a channel indicates that no more values will be sent on it.
* This unblocks any receive operations (
<-c
) in the goroutines.
Goroutines¶
Launching a Goroutine¶
go func() {
<-c
out <- i + v
}()
go
: When go
comes before a function call, it starts a new goroutine, a
lightweight thread managed by the Go runtime.
* func() {}()
: Defines and immediately invokes an anonymous function.* This function first tries to receive a value from channel
c
(which blocks until
c
is closed), then calculates i + v
and sends the result to the out
channel.
WaitGroups¶
Wait Groups are a concurrency mechanisms in Go used for coordinating the execution
of multiple goroutines.
They let your program wait for a collection of goroutines to finish before
continuing.
WaitGroups should only be passed to functions as pointers.
WaitGroups are a part of the sync
package.
A waitgroup essentially acts as a counter.
You add to the counter when you start goroutines.
* Each goroutine signals (or decrememnts the counter) when it completes.
The program waits until the counter reaches zero before moving on.
* The goroutine should call the Done()
method when it finishes.
defer wg.Done()
WaitGroup
Example¶
Example of using a waitgroup to execute multiple goroutines:
func worker(id int, wg *sync.WaitGroup) { // Dereference the wg when it is received
defer wg.Done()
fmt.Printf("Starting job ID: %v\n", id)
// pretend to do some work
time.Sleep(2 * time.Second)
fmt.Printf("Job done: %v\n", id)
}
func main() {
var wg sync.WaitGroup
// Start 3 worker Goroutines
for i := 1; i < 3; i++ {
wg.Add(1)
go worker(i, &wg) // Pass it in as a pointer
}
wg.Wait() // Wait for all workers to finish
fmt.Println("All goroutines have finished~!")
}
var wg sync.WaitGroup()
: Initialize the WaitGroup
object.wg.Add(1)
: Add to the goroutine counter when starting a goroutine.go worker(i, &wg)
: Start a goroutine. Each goroutine receives a pointer to the WaitGroup
.defer wg.Done()
: Run the wg.Done()
method at the end of the function.wg.Wait()
: Block the execution of the rest of the program until the counter reaches 0
.
Receiving Results and Output¶
println(<-out + <-out)
<-out
: Receives a value from the out
channel.*
println
: Built-in function that prints its arguments to standard error.* Here, it prints the sum of the two values received from
out
.
Import Statement¶
If your program doesn't explicitly import any packages, it can only use builtin
functions and types (make
, chan
, map
, for
, range
, go
, func
, close
,
and println
), which do not require an import statement.
Pain Point¶
Go modules are the biggest pain point of Go.
go mod tidy
doesn't do what it's supposed to do.
Managing dependencies sometimes needs to be done manually in a go.mod
file.
Creating a New Project in Go¶
go mod init github.com/kolkhis/<name>
go.mod
file and shows the Go version.
This is a popular naming convention for modules since GitHub is essentially the package manager for Go.
If you were going to install this program:
go install github.com/kolkhis/<name>@latest
Misc¶
- How you structure the project will depend on what kind of project it is (cmd or pkg).
- You can't use the walrus operator in the global scope, only inside functions.
- It's safer to default to private (non-exported), so you're not committed to maintaining it.
- You cannot export anything from
main
.
testdata
is special directory that is not recursed during compilation.
go get -u # Download dependencies
The COMP_LINE
environment variable is set when bash is doing completion.
To enable self-completion:
complete -C 'script-name' script-name