nil maps in Golang

I’ve been a bit confused for a while over when a map in Golang gets created with a value of nil (its zero value) and when it does not, so I’m writing this to help me remember. Let’s look at various ways to initialize a map: package main import "fmt" func main() { m1 := map[string]string{} // initializes map m2 := make(map[string]string) // also initializes map var m3 map[string]string // does NOT initialize the map! HERE BE DRAGONS! fmt.Println(m1 == nil) // false fmt.Println(m2 == nil) // false fmt.Println(m3 == nil) // true m1["foo"] = "bar" m2["foo"] = "bar" m3["foo"] = "bar" // panics because the map is NOT initialized, i.e., map is still nil } My takeaways from this are: ...

2023-01-07

Built-in Go HTTP server

Go has a built-in HTTP server in net/http. Here’s me playing around using it: package main import ( "fmt" "net/http" "strings" ) func hello(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "hello\n") } func details(w http.ResponseWriter, req *http.Request) { resp := []string{} resp = append(resp, "Request Details:") resp = append(resp, fmt.Sprintf("- Request proto: %s", req.Proto)) resp = append(resp, fmt.Sprintf("- Headers:")) for name, val := range req.Header { resp = append(resp, fmt.Sprintf("\t- %s: %s", name, val)) } resp = append(resp, "\n") fmt.Fprintf(w, strings.Join(resp, "\n")) } func main() { http.HandleFunc("/hello", hello) http.HandleFunc("/details", details) fmt.Println("Started.") http.ListenAndServe("127.0.0.1:9999", nil) fmt.Println("Exiting.") } The /details endpoint returns interesting stuff about the HTTP request, like its protocol and headers: ...

2022-10-14

Fixing: File is not `goimports`-ed (goimports)

The other day, I was linting my Go code with golangci-lint when I got this error: File is not `goimports`-ed (goimports) I examined the file and found nothing amiss, but the linter insisted something was wrong. Eventually, I realized I had used spaces to indent the file rather than tabs. Changing the indent character to tabs fixed it. I then ran into the same error on a second file. This time, I had a comment which was wrong. There were two spaces before the first word in the comment (see below). ...

2022-10-05

Go and pass by value

Go normally uses pass-by-value for function calls. When you pass a variable into a function or method, Go will (under the hood) create a new variable, copy the old variable’s value into it, and then pass the new variable (not the original variable) into the function or method. Non-pointer values These types behave as described above and are sometimes called non-pointer values: Strings Ints Floats Booleans Arrays Structs Here’s an example of how these work. Note that the variables myString and si do not point to the same memory address: ...

2022-09-01

Constants in Go

Constants syntax I always have a hard time remembering the syntax of declaring constants in Go. So here’s some reminders to myself. It is possible to declare constants one per line like you’d expect. Note that the there are typed and untyped constants as shown here below. package main import "fmt" const vermKnid string = "scram" const fox = "foxes likes loxes" func main() { fmt.Println(vermKnid) fmt.Println(fox) } But you can also use a constant declaration group, which does the same thing but is easier to read if you’re declaring a bunch of constants. ...

2022-08-16

Mutexes and concurrent access in Go

Golang has mutexes (short for mutually exclusion) to manage concurrent access to a shared object via multiple goroutines. Here’s an example (taken from the excellent Go by Example: Mutexes). package main import ( "fmt" "sync" ) type Counter struct { mu sync.Mutex count map[string]int } func (c *Counter) Inc(name string) { c.mu.Lock() defer c.mu.Unlock() c.count[name]++ } func main() { cnt := Counter{count: map[string]int{"james": 0, "spartacus": 0}} var wg sync.WaitGroup increment := func(name string, n int) { for i := 0 ; i < n ; i++ { cnt.Inc(name) } wg.Done() } wg.Add(3) go increment("james", 100000) go increment("james", 100000) go increment("spartacus", 10000) wg.Wait() fmt.Println(cnt) } This code defines a simple Counter struct with a single method named Inc to increment the count map. Inc is responsible for managing the locking and unlocking of the mutex with c.mu.Lock() to lock it and defer c.mu.Unlock() to unlock it as the method is returning. ...

2022-07-22

Go routines and WaitGroups

Working with Goroutines is simple. You simply pre-prend your function call with go and you’re off and running. Here’s an example which calls the expensive() function five times before exiting. package main import ( "fmt" "time" ) func expensive(id int) { fmt.Printf("Worker %d starting\n", id) time.Sleep(time.Second) // Oh, so expensive! fmt.Printf("Worker %d ending\n", id) } func main() { for i := 0; i < 5; i++ { go expensive(i) } fmt.Println("Exiting!") } But it has a problem… running the program shows output like this: ...

2022-07-18

Closures in Go

In Go, it’s pretty easy to write an anonymous function. package main import "fmt" func main() { func(s string){ fmt.Println("s is:", s) }("hello world") } Running the above code produces: $ go run main.go s is: hello world You can even assign the anonymous function to a variable like so: package main import "fmt" func main() { myFunc := func(s string){ fmt.Println("s is:", s) } myFunc("hello again, world!") } Now we get this output from the above code: ...

2022-07-13

Generics in Go 1.18

In Go 1.18, we get access to Genrics. Huzzah! Here’s an basic example of how to use them: package main import ( "fmt" ) func PrintAge[age float32 | int64](myAge age) { fmt.Printf("age: %s \t type: %T\n", myAge, myAge) } func main() { var age1 float32 = 30.5 var age2 int64 = 32 PrintAge(age1) PrintAge(age2) } Running the above program gives us the below output. You can see that it happily accepted the float32 and int64 values without issue. age: %!s(float32=30.5) type: float32 age: %!s(int64=32) type: int64 This saves us from having to either (a) define a separate PrintAge function for every type of number we want to accept or (b) writing the PrintAge to accept type interface{} which would then accept types we don’t want to accept (and having to mess with type assertions and all that). ...

2022-07-12

Go defer

defer in go is used to run a function later on. Specifically, the specified function will run when the enclosing function returns. There’s no clean equivalent to this in Python that I’ve found. The quintessential example is closing a file handle as your program exits. Example package main import ( "fmt" "os" ) func main() { file := createFile("/tmp/testingdefer.txt") defer closeFile(file) writeFile(file) } func createFile(path string) (file *os.File) { fmt.Println("creating file: ", path) file, _ = os.Create(path) // skipping error handling for brevity return } func writeFile(file *os.File) { fmt.Println("writing file") fmt.Fprintln(file, "here is some data for you!") } func closeFile(file *os.File) { fmt.Println("closing file") file.Close() // skipping error handling for brevity }

2021-07-20