Golang datatypes
Documentation
builtin types https://pkg.go.dev/go/types@go1.18.3 builtins (primitive types) https://pkg.go.dev/builtin@go1.18.3
Literal Types
nil // NULL "foo" // string 123 // int (cpu-arch dependent bitsize) 3.14 // float64 6.2e25 // float64 true, false // bool
Boolean
var condition bool = true
Local Primitives
Go lets you create and modify your own local varitions of it's types.
You can then add methods within your local scope.type Int int func (i Int) Double() Int { return i * 2 } func main() { five := Int(5) fmt.Println(five.Double() == 10) }
Text
String
Go's strings are bytestrings by default.
The are treated as arrays of bytes.
- Strings are immutable.
- Strings are UTF-8
TODO:
write some best pratices for handling multi-byte characters
var name string = "will" // string name := "will" // string fmt.Sprintf("[%s] %s", "error", "thing happened") // no string interpolation, use Sprintf // slices var ascii_code uint8 = name[1] // 2nd char from string var char string = string(ascii_code) // get string from UTF-8 codepoint number bytestring := []byte("hi") == [104, 105] // string as bytes (uint8) // operators msg := "hello " + "world" // concatenationgolang doesn't have string interpolation, but
os.Expand()
implements simple templatingos.Expand() example
func expander(s string) string { switch s { case "user": return "will" case "msg": return "how are you?" default: return "unknown" } } os.Expand("Hello ${user}. ${msg}", expander) // "Hello will. how are you?"Rune
Runes are UTF-32 strings.
(Each character is auint32
)
Numeric
Integers
Integer sizes are expressed by their bit-size.
signed
int // (however many bits your CPU word-size is) int8 // 128 - 127 int16 // 32,768 - 32,767 int32 // 2,147,483,648 - 2,147,483,647 int64 // 9,223,372,036,854,775,808 - 9,223,372,036,854,775,807unsigned
uint // (however many bits your CPU word-size is) uint8 // 0 - 255 uint16 // 0 - 65,535 uint32 // 0 - 4,294,967,295 // uint64 does not existBytes
Same as
uint8
.math/big
Slow, but handles numbers of any size.
Complex Numbers
Go lets you represent complex/imaginary numbers.
var num complex64 = 1 + 2i var num complex128 = 1 + 2i var num complex64 = 2i // alternative for 1 + 2i var num complex64 = complex(1, 2) // alternative for 1 + 2iYou can also extract the complex/imaginary part of the number.
var num complex64 = 1 + 2i real(num) // the real number component imag(num) // the imaginary number component
Pointers
uintptr // pointer type (any word-size) var a int = 123 var b *int = &a // declare a pointer for an 'int' type a == *b // de-reference 'b' to get value &a == b // reference 'a' to get memory addresspointer aritmetric is not supported by go, if required you can workaround this in the
unsafe
package.You can initialize a type using a pointer to it
type Foo struct { a int } func main() { var foo = *Foo foo = new(Foo) // create instance of 'foo' foo.a = 1 // assign pointer foo.a (*foo).a = 1 // assign pointer foo.a }
Error
Go's
panic()
s are roughly analogous with exceptions,
but it is much more common to use error types to communicate errors.func sudo(user string, command string) (exitcode int, error) { // ... if success { return 0, nil } return 1, fmt.Errorf("User does not exist") }
Collections
Arrays
Arrays are statically-sized, homogenous collections of items,
stored contiguously in memory.building arrays
family := [2]string{"will", "alex"} // create 2-item array with these elements family := [...]string{"will", "alex"} // create array whose size matches provided elements var family [2]string // create 2-item array, then assign elements family[0] = "will" family[1] = "alex" var families string[3][2] // create an empty nested array families := [...]string{ // create nested array using literals [...]string{"will", "alex"}, [...]string{"tom", "fiona"}, [...]string{"alex", "morgan"}, }array functions
len(family) == 2While arrays are mutable, when assigning an array to a new variable,
you're actually duplicating the array.numbers := [...]int{1, 2, 3} copy_of_numbers := numbers copy_of_numbers[1] = 9 // <-- does not change 'numbers' fmt.Println(numbers) // [1 2 3] fmt.Println(copy_of_numbers) // [1 9 3]Rather than passing array-data to a function, in most cases, you probably want to pass a pointer to it.
numbers := [...]int{1, 2, 3} ref_to_numbers := &numbers // <-- get pointer to 'numbers' ref_to_numbers[1] = 9 fmt.Println(numbers) // [1 9 3] fmt.Println(ref_to_numbers) // &[1 9 3]Slices
The Slice datastructure is a resizable abstraction built overtop of an array.
see https://go.dev/blog/slices-introA slice's datastructure is composed of:
- pointer to array element
- length of slice
- capacity (length) of underlying array
Some properties:
- Assigning a slice to another variable will not duplicate the data
- Changing a slice will change the underlying array
- Resizing a slice is fairly cheap. Methods exist to duplicate array into a larger one when required.
building slicesnumbers := []int{1, 2, 3} // build an empty slice numbers = make([]int, 3, 3) // `make(type, length, [capacity])` numbers = make([]int, 3) // if not specified, capacity matches lenth // build slices from arrays, or other slices numbers := [...]{1, 2, 3, 4, 5} // array {1, 2, 3, 4, 5} mySlice := numbers[:2] // slice {1, 2, 3}slice functions
len(numbers) == 3 // length cap(numbers) == 3 // capacity // starting at 'length', add items to the array, growing if necessary // (doubles ex. 2, 4, 8, 16, ...) append(numbers, 4, 5) append(numbers, otherSlice...) // concatenate/extend another slice to an existing oneCapacity, and Growing Slices
One interesting property of append, is that it assumes you want to grow the underlying array.
This makes it convenient to start with length 0, and grow your array as needed.
This can be costly, since each appended item will create a new array, and copy the old one into it.You can manipulate the slice's capacity to grow pre-allocate arrays, and grow your slice in increments that make sense to your program.
// Yuck -- each append creates a new (len+1) array, and copies old array into it numbers = make(int[], 0) // length=0, capacity=0 append(numbers, 1) append(numbers, 2)// Nice - we won't need to resize array until we add the 6th item numbers = make(int[], 0, 5) append(numbers, 1) append(numbers, 2)// Nicer - once we hit the 6th item, let's grow in increments of the capacity numbers := make([]int, 0, 5) numbers = append(numbers, 1, 2, 3, 4, 5) buf := make([]int, len(numbers), cap(numbers) * 2) copy(buf, numbers) numbers = buf fmt.Println(numbers) // [1 2 3 4 5] fmt.Println(cap(numbers)) // 10
Compound Types
Maps
Maps are unordered, and must be homogenous.
They can be nested.building maps
var user_ids = map[string]int{ "Alex": 1, "Will": 2, // ... } user_ids["Alex"] == 2Go only supports comparable datatypes as keys.
There is no magic method you can use to make a type comparable, the comparable types are strictly:valid key types
// primitives booleans integers floats strings pointers arrays channels // compound interface values # if all fields are same type, and values are identicalmap methos
delete(myMap, "myKey") // remove item from map len(myMap) // number of items val = myMap["myKey"] // retrieve value from map (zero type if not set) val, ok := myMap["myKey"] // retrieve value from map, with boolean indicating if map contained keyStructs
structs are mutable compound objects with fixed fields.
Field names follow the same casing rules for exported/private fields.
struct values are stored directly (not by reference) - copied structs will have separate values.buiding structs
// declared struct type Animal struct { age int // lowercase, so private outside of package Name string // uppercase, so exported outside of package } // anonymous struct pet := struct(age int, name string){ age: 2, name: "maize" }initializing structs
pet := Animal{age: 2, name: "maize"} // keyword init pet := Animal{2, "maize"} // positional initstruct composition (embedding)
type Animal struct { age int Name string } type Cat struct { Animal // <-- include all fields from Animal whiskers int } // initializing in constructor // requires you to assign values on the embedded struct Cat{ Animal: Animal{age: 2, Name: "maize"} whiskers: 4, } // outside of constructor, // changing values can be done without knowledge of composition cat := Cat{} cat.age = 2 cat.Name = "maize" cat.whiskers = 4struct tags
type Roommate struct { FirstName string `Roommmate's firstname` // text between `` is a tag } // tags can be accessed programmatically type := reflect.TypeOf(Roommate{}) field, _ := type.FieldByName("Name") fmt.Println(field.Tag)methods/functions
pet.species == "cat" // get fields