GoLang
GoLang is a relatively new programming language. I consider it a
“middle level” language because it is strictly-typed, compiled, and
provides more protections than C. It also has a garbage collector,
lightweight concurrency models, and builtin chan
and error
types.
Hello World
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
Concurrency
GoLang offers a concurrency model using goroutines, which are lightweight threads managed by the Go runtime.
go func() {
// do something
}()
Channels
Channels are a “typed conduit through which you can send and receive
values with the channel operator, <-
.” It is basically a buffer
to send and receive data. Sending will block when the buffer is
full, and receiving will block when the buffer is empty. By default, a
channel is unbuffered, but you can change the size on initialization.
package main
import "fmt"
func main() {
// create a buffered channel of length 10
ch := make(chan int, 10)
for i := 0; i < 10; i++ {
ch <- i
}
for i := 0; i < 10; i++ {
fmt.Println(<-ch)
}
}
Maps
Maps are the builtin dict
of GoLang. They can be used to create
sets, avoid O(n^2)
algorithms, and of course lookup tables.
// create a map
m := map[string]string{}
m := make(map[string]string)
// check item exists in map
if item, ok := m["hello"]; ok {
// item exists
}
// convert an array to a set
m := make(map[string]struct{}, len(arr))
for _, item := range arr {
m[item] = struct{}{}
}
Note: Maps are not safe for concurrent use.
Note: map[T]struct{}
is the idiomatic way to create a set because the
size of an empty struct is 0.
Pointers
GoLang, like C, has pointers, but GoLang’s pointers are safer than
C. You cannot (easily) convert between two different pointer types,
and the pointer operator is the same as the struct operator. This
means you don’t have to worry about whether your object is a pointer
or not, you can do object.Attribute
and GoLang will figure out
whether it needs to dereference the pointer first or not.
Methods
GoLang defines struct methods using pointer receivers, which allows
for the OOP style of struct.func()
.
package main
import "fmt"
type Example string
func (e *Example) Examp() string {
return "Examp: " + string(*e)
}
func main() {
e := Example("hello")
fmt.Println(e.Examp())
}
Loops
GoLang uses for
for all types of loops. Labels can be used to
break or continue when in a nested context.
// infinite loop
for { }
// standard for loop
for i := 0; i < 10; i++ { }
// while loop
i := 0
for i < 10 { i++ }
// iterate over slice
for index, item := range slice { }
// iterate over a map
for key, value := range m { }
// labeled loop
loop:
for {
break loop
}
Interfaces
Go uses interfaces for a sort of “duck typing.” An interface is defined as a group of methods that must exist on a concrete type. They are generally best used as inputs for a library to allow users to implement and use their own types.
type Example interface {
Examp() string
}
Note: The empty interface interface{}
(now known as any
) is a way to
accept any possible type.
An interface can be used in place of a concrete type, but only the methods defined in the interface will be accessible. If you want the underlying type of an interface, type coercion can be used.
type Foo struct { id int }
func (f Foo) Examp() string {
return "Foo!"
}
func doSomething(e Example) {
fmt.Println(e.Examp())
if f, ok := e.(Foo); ok {
fmt.Println("e is Foo with ID", f.id)
}
}
Generics
As of Go 1.18, generics exist in the language for very limited use cases. The idea being that generics would be a solution for code that you would need to generate for different types. Generics in Go aren’t fully monomorphized in order to keep fast compile times. Most of the time, generics will be resolved at runtime.
func add[T ~int | ~float64](a, b T) T {
return a + b
}
Note: ~
is referencing the underlying type.
Generics can be constrained by interfaces as well. See the exp/constraints package for some useful constraints.
Style
GoLang has a very opinionated style.
- Tabs not spaces
- Capitalized functions and attributes are considered exported
- Exported code should have a comment explaining it (code is also docs)
{
on the same line
Unit Tests and Benchmarks
GoLang has builtin support for unit tests and benchmarking. Test
files should end in _test.go
for Go to automatically find them.
Functions must start with Test
or Benchmark
as well. Run with
go test -bench .
.
package main
import "testing"
func TestExamp(t *testing.T) {
e := Example("hello")
got := e.Examp()
want := "Examp: hello"
if got != want {
t.Errorf("got %s, want %s", got, want)
}
}
func BenchmarkExamp(b *testing.B) {
e := Example("hello")
for i := 0; i < b.N; i++ {
e.Examp()
}
}
Tools
go build
: build Go binariesgo test
: unit testsgo fmt
: format code according to the Go stylego get
: install remote packagesgo vet
: static code analyzergo run
: shortcut to build and execute code