3
votes

I am working on a sample program in golang which is as follows

package main

import (
    "fmt"
)

type thing [2]byte

func walk(things []thing, idx int) []byte {
    var match []byte
    for i, thing := range things {
        if i == idx {
            match = thing[:]
        }
    }
    return match
}


func main() {
    ta := []thing{ thing{'W','A'}, thing{'O','R'} }
    m := walk(ta, 0)

    tb := []thing{ thing{'C','A'}, thing{'W','Y'}, thing{'N','V'} }
    n := walk(tb, 1)

    fmt.Printf("m = %s\n", m)
    fmt.Printf("n = %s\n", n)
}

The output is:

m = OR
n = NV

I am not sure why this is the case when type thing [2]byte is an array of size 2 and ta is type []thing.

Now when you write the same code as this using [][]byte

package main

import (
    "fmt"
)

func walk(things [][]byte, idx int) []byte {
    var match []byte
    for i, thing := range things {
        if i == idx {
            match = thing[:]
        }
    }
    return match
}

func main() {
    ta := [][]byte{[]byte{'W', 'A'}, []byte{'O', 'R'}}
    m := walk(ta, 0)

    tb := [][]byte{[]byte{'C', 'A'}, []byte{'W', 'Y'}, []byte{'N', 'V'}}
    n := walk(tb, 1)

    fmt.Printf("m = %s\n", m)
    fmt.Printf("n = %s\n", n)
}

The output is

m = WA
n = WY

I am confused by these different behaviours of slices? Printing out ta[:] when ta is type thing []byte and ta[:] when ta is [][]byte is the same

2
It's not the same - thing is type [2]byte, not type []byte. That is, it's an array, not a slice. The semantics are not the same.Adrian
@manugup1 allow me to point something over the code-design that may not right: if you know that you have [2]byte as elements of the slice, why, in your first example, are returing []byte? If you return a [2]byte (an array) the algorithm works as expected. See for youself what i'm trying to say: play.golang.org/p/wOGaJ9Pwtck. Nevertheless this is a great example for understaning the slice internals....Victor
BTW: this example goes straith to the matter: play.golang.org/p/GelDPKlNFO-Victor
@manugulp1 yesterday i saw here a comment that give me thanks for providing more insight about the actual problem. You shouldn't erase comments like that, actually they help, by giving me positive feedback and encouraging me to keep contribuiting. Don't forget this place is, after all, a CoP (Community of Practices).Victor
@Victor I have not removed any comment (at least not intentionally)! In fact, that comment was what helped me understand it the concept really well. I was surprised it was gone later.manugupt1

2 Answers

2
votes

In one case, an individual thing is an array ([2]byte), and in the other, it's a slice ([]byte). In the first case, you're slicing an array into match, which gives you a new slice pointing at your loop iteration variable. In the second case, you're re-slicing an existing slice, so your new slice points at that slice's underlying array, even after the loop changes out the local slice variable.

Because your loop keeps running after you find your match, you find your match, slice it, then keep iterating, changing the value of the local loop iterator. If you did:

    if i == idx {
        return thing[:]
    }

instead, the issue disappears: https://play.golang.org/p/Uq4DbEGlGX8

2
votes

Two cases are not the same.

In the first case, you are working with [][2]byte (slice of arrays) not with [][]byte (slice of slices).

var match []byte
for i, thing := range things { // (2) change array on each iteration
    fmt.Printf("Thing %v", thing)
    if i == idx {
        match = thing[:] // (1) slice refers to array   
    }
}
return match // (3) here match slice refers to the last item of things

One of solutions here is adding break statement after match = thing[:]. It ends the loop and match will refer to expected array.

Just to clarify what is the actual issue here, the problem is that you are creating an slice that refers to an array that is overwritted in each n-iteration with the values of the correspondant n-th element of the slice of 2 bytes array. So if you don't stop iterating the slice will get the values of the last element.

Using a for .. := range "hides" a little bit this fact. I guess if you write the same code with a for i;i<n;i++, you could get a better understanding of what is actually happening: https://play.golang.org/p/z3hCskZ9ezV