Skip to content

Instantly share code, notes, and snippets.

@geberl
Last active December 22, 2019 10:13
Show Gist options
  • Save geberl/db975428f8aaed74097835bfeeaff16e to your computer and use it in GitHub Desktop.
Save geberl/db975428f8aaed74097835bfeeaff16e to your computer and use it in GitHub Desktop.
Pointers vs literals & Swift-style safe unwrapping
package main
import "fmt"
type person struct {
firstName string
lastName *string
addressAsValue address
addressAsPointer *address
childrenAsSlice []person
childrenAsPointer *[]person
}
type address struct {
city string
zip uint32
}
// String is a convenienve for converting a string literal to a pointer to a string.
func String(s string) *string { return &s }
// Int is a convenience for converting an int literal to a pointer to an int.
func Int(i int) *int { return &i }
// Int32 is a convenience for converting an int32 literal to a pointer to an int32.
func Int32(i int32) *int32 { return &i }
// Bool is a convenience for converting a bool literal to a pointer to a bool.
func Bool(b bool) *bool { return &b }
func main() {
firstPerson := person{firstName: "jim", lastName: String("beam")}
// firstAddr := address{city: "munich", zip: 80000}
// firstPerson := person{firstName: "jim", lastName: String("beam"), addressAsPointer: &firstAddr}
fmt.Println(firstPerson) // {jim 0xc000010200 { 0} <nil> [] <nil>}
fmt.Println("---")
fmt.Println(firstPerson.firstName) // jim
fmt.Println(firstPerson.lastName) // 0xc0000621e0
fmt.Println(*firstPerson.lastName) // beam
fmt.Println("---")
// This works in both cases
fmt.Println(firstPerson.addressAsValue) // { 0} = default value empty string for string and 0 for uint32
fmt.Println(firstPerson.addressAsPointer) // <nil> = default value nil for pointer
fmt.Println(firstPerson.childrenAsSlice) // [] = default value empty slice for slice
fmt.Println(firstPerson.childrenAsPointer) // <nil> = default value nil for pointer
fmt.Println("---")
// Going one level deeper just works for values, not for pointers
fmt.Println(firstPerson.addressAsValue.zip) // 0
// fmt.Println(firstPerson.addressAsPointer.zip) // "panic: runtime error: invalid memory address or nil pointer dereference"
fmt.Println("---")
// Compare Swift pattern for safe-unwrapping of optional:
// var title: String?
// if let safeTitle = title {
// ...
// }
// Swift-style "safe-unwrapping", only possible if defined as pointer
if addr := firstPerson.addressAsPointer; addr != nil {
fmt.Println(addr.zip)
}
// This works and as expected prints nothing because the slice is empty
for _, child := range firstPerson.childrenAsSlice {
fmt.Println(child.firstName)
}
// This is invalid code, "cannot range over firstPerson.childrenAsPointers", a pointer is a single memory address
// for _, child := range firstPerson.childrenAsPointer {
// fmt.Println(child.firstName)
// }
// Dereferencing the pointer leads to valid code but throws "panic: runtime error: invalid memory address or nil pointer dereference" if the slice is empty
// for _, child := range *firstPerson.childrenAsPointer {
// fmt.Println(child.firstName)
// }
// - Underneath a slice is already a kind of pointer (pointer to first element + length of slice + capacity of slice)
// - Explicitly using a pointer to a slice "*[]" is useless in terms of reducing the memory footprint
// - And it might cause the program to fatally error if someone decides to dereference the pointer and range over it
// - *[] is kind of a code smell
// - There is only one legit use for pointer to a slice:
// If multiple sections of the program need to share the same slice
// So modifications to the slice itself are reflected in other sections of the program
// This is rarely what you want, however, and is not thread-safe without locking
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment