Arpith Siromoney šŸ’¬

Stumbling through Go

Today Iā€™m XORā€˜ing byte arrays; fortunately, this blog post seems to indicate that plain strings will do:

Strings are built from bytes so indexing them yields bytes, not characters. A string might not even hold characters. In fact, the definition of ā€œcharacterā€ is ambiguous and it would be a mistake to try to resolve the ambiguity by defining that strings are made of characters.

Iā€™m too lazy to input two strings each time I run the program, so Iā€™m going to read them from a file

dat, err := ioutil.ReadFile("challenge2.in")

Since there is one more error Iā€™ll have to check for (Sscanf) Iā€™m doing as the page suggests and adding a small function on top,

func check(e error) {
    if e != nil {
        panic(e)
    }
}

I donā€™t really care about the number of items successfully scanned, so I tried

 _, e := fmt.Sscanf(string(dat),"%x\n%x",&string1,&string2)

which seemed to work. Googling for a link to explain it and found out I was wrong about tuples yesterday, apparently the multiple values returned by functions do not represent a first class object so I googled multiple returns which lead me to the section on error handling which mentioned

This is only an example but real library functions should avoid panic. If the problem can be masked or worked around, itā€™s always better to let things continue to run rather than taking down the whole program. One possible counterexample is during initialization: if the library truly cannot set itself up, it might be reasonable to panic, so to speak.

I canā€™t really do anything without reading the file and scanning the strings so Iā€™m going to leave my check function as it is. I also found my blank identifier. As for the actual XORā€™ing of the strings, itā€™s been explained before:

n := len(string1)
b := make([]byte, n)
for i := 0; i < n; i++ {
    b[i] = string1[i] ^ string2[i]
}

So! Unlike yesterday, letā€™s read a bit more about []byte: make says something interesting,

Slice: The size specifies the length. The capacity of the slice is equal to its length. A second integer argument may be provided to specify a different capacity; it must be no smaller than the length, so make([]int, 0, 10) allocates a slice of length 0 and capacity 10.

Which this fascinating blog post clarifies.

What if we want to grow the slice beyond its capacity? You canā€™t! By definition, the capacity is the limit to growth. But you can achieve an equivalent result by allocating a new array, copying the data over, and modifying the slice to describe the new array.

The blog post goes on to talk about how append works, but I saw this extend function and wondered where the old slice goes.

// Slice is full; must grow.
// We double its size and add 1, so if the size is zero we still grow.
newSlice := make([]int, len(slice), 2*len(slice)+1)
copy(newSlice, slice)
slice = newSlice  

Go has garbage collection!

When possible, the Go compilers will allocate variables that are local to a function in that functionā€™s stack frame. However, if the compiler cannot prove that the variable is not referenced after the function returns, then the compiler must allocate the variable on the garbage-collected heap to avoid dangling pointer errors. Also, if a local variable is very large, it might make more sense to store it on the heap rather than the stack.

In the current compilers, if a variable has its address taken, that variable is a candidate for allocation on the heap. However, a basic escape analysis recognizes some cases when such variables will not live past the return from the function and can reside on the stack.

The faq lead me to this explanation of allocation with make

The built-in function make(T, args) serves a purpose different from new(T). It creates slices, maps, and channels only, and it returns an initialized (not zeroed) value of type T (not *T). The reason for the distinction is that these three types represent, under the covers, references to data structures that must be initialized before use. A slice, for example, is a three-item descriptor containing a pointer to the data (inside an array), the length, and the capacity, and until those items are initialized, the slice is nil. For slices, maps, and channels, make initializes the internal data structure and prepares the value for use.

Finally, back to the actual program I was writing, Iā€™ve replaced for i := 0; i < n; i++ with for i := range b. Note that you canā€™t replace b[i] with bvalue (if you had used for i, bvalue := range b). for statements are interesting,

In its simplest form, a ā€œforā€ statement specifies the repeated execution of a block as long as a boolean condition evaluates to true. The condition is evaluated before each iteration. If the condition is absent, it is equivalent to the boolean value true.

Like a while loop!