Go is a garbage collected language. When a new variable is allocated (either with make or through literal declarations), the Go escape analyzer (which is run at compile time) determines if a particular variable should escape to the heap, or if it can remain on the stack. The garbage collector cleans heap-allocated data.
Go has all of the pillars except for 4. A common misconception is that Go's type embedding constitutes inheritance, but embedding is only syntatic sugar around composition. For example, an embedded type does not have access to the "children" structs. Thus, it doesn't have true inheritance.
A goroutine is essentially a "thread" of execution (not to be confused with an OS thread) that is managed by the Go runtime. goroutines are multiplexed and scheduled onto OS threads according to the needs of the goroutine. goroutines that are waiting on IO are put into a sleep state until they are ready to handle the IO.
Functional Options are a style of specifying optional arguments. This is needed to get around the fact that Go does not have default parameter values. It works by creating a type that is defined as a function that takes, as its input a config struct, and returns nothing. The function will set a specific attribute in the config to the value passed in to the constructor. For example, typeFuncOptfunc(c*Config). Here is an example:
You can see that PersonName is one such constructor that takes in a string, and it returns a function that modifies the value of the Person struct with the value "name" passed into the PersonOpt constructor. It relies on the fact that Go functions can be closures
A rune is an alias for int32. It represents a code point in the Unicode standard. The elements of a string (which are just singular bytes) can be converted to a rune through a simple rune(elem) type conversion. It's important to know that some characters in the Unicode specification can be represented by different bytes. For example, à can be represented in two ways: either it's explicit code point U+00E0, or through a sequence of two separate code points of U+0061 (which is the lowercase a) and U+0300 (which is the "combining" grave access point, which can be applied to any character). This form of character representation would require two elements in a string type in Go because it requires two bytes, but can be easily represented by a single rune.
Slices are a metadata view over a dynamically-sized array. A slice contains:
A reference to the underlying array
The length of our view in the underlying array (which serves as a boundary)
A capacity, which is the the number of elements between our pointer to the underlying array, and its end.
Slices are automatically expanded by the runtime when we run out of capacity. It does this by first allocating a new array that is twice the size of the original, then copies the contents of the new array. You can see this expansion behavior as such:
It moves the literal values $0x16 and $0x21 onto the memory location pointed to in (%rsp) and (%rsp)+0x8. The results are then retrieved off of the stack and are put into %rax.
In 1.18, Go prefers passing arguments through registers:
Something else Menno noted was that Go doesn't use the push instruction and instead uses mov to push values onto the stack. I was initially confused by this as well, but he notes that mov is generally faster: https://agner.org/optimize/instruction_tables.pdf
Accessing the function pointer if you call a generic function with a pointer to an interface requires three dereferences:
Text Only
MOVQ ""..dict+48(SP), CX # load the virtual table from the stack into CX
0094 MOVQ 64(CX), CX. # dereference the dictionary to get the *itab
0098 MOVQ 24(CX), CX. # Offset by 24 bytes to load the function pointer within *itab
Another interesting point, passing an interface to a generic function where the interface is a superset of the type constraint is what the author calls a "footcannon," because the compiler does this nonsense:
The compiler is asserting that the interface you passed does indeed implement the interface in the type constraint (runtimeassertI2I), and it grabs the *itab of the interface in the type constraint. This tanks performance.