unexpected slice append behaviour

前端 未结 3 1820
臣服心动
臣服心动 2021-02-06 12:58

I encountered weird behaviour in go code today: when I append elements to slice in loop and then try to create new slices based on the res

相关标签:
3条回答
  • 2021-02-06 13:08

    Don't assign append to anything other than itself.

    As you mention in the question, the confusion is due to the fact that append both changes the underlying array and returns a new slice (since the length might be changed). You'd imagine that it copies that backing array, but it doesn't, it just allocates a new slice object that points at it. Since i never changes, all those appends keep changing the value of backingArray[12] to a different number.

    Contrast this to appending to an array, which allocates a new literal array every time.

    So yes, you need to copy the slice before you can work on it.

    func makeFromSlice(sl []int) []int {
        result := make([]int, len(sl))
        copy(result, sl)
        return result
    }
    
    func main() {
        i := make([]int, 0)
        for ii:=0; ii<11; ii++ {
            i = append(i, ii)
        }
        j := append(makeFromSlice(i), 100)  // works fine
    }
    

    The slice literal behavior is explained because a new array is allocated if the append would exceed the cap of the backing array. This has nothing to do with slice literals and everything to do with the internals of how exceeding the cap works.

    a := []int{1,2,3,4,5,6,7}
    fmt.Printf("len(a) %d, cap(a) %d\n", a, len(a), cap(a))
    // len(a) 7, cap(a) 7
    
    b := make([]int, 0)
    for i:=1; i<8, i++ {
        b = append(b, i)
    }  // b := []int{1,2,3,4,5,6,7}
    // len(b) 7, cap(b) 8
    
    b = append(b, 1)  // any number, just so it hits cap
    
    i := append(b, 100)
    j := append(b, 101)
    k := append(b, 102)  // these work as expected now
    
    0 讨论(0)
  • 2021-02-06 13:13

    There is also a little bit simpler way to implement copyAndAppend function:

    func copyAndAppend(source []string, items ...string) []string {
        l := len(source)
        return append(source[:l:l], items...)
    }
    

    Here we just make sure that source has no available capacity and so copying is forced.

    0 讨论(0)
  • 2021-02-06 13:18

    If you need a copy of a slice, there's no other way to do it other than, copying the slice. You should almost never assign the result of append to a variable other than the first argument of append. It leads to hard to find bugs, and will behave differently depending on whether the slice has the required capacity or not.

    This isn't a commonly needed pattern, but as with all things of this nature if you need to repeate a few lines of code multiple times, then you can use a small helper function:

    func copyAndAppend(i []int, vals ...int) []int {
        j := make([]int, len(i), len(i)+len(vals))
        copy(j, i)
        return append(j, vals...)
    }
    

    https://play.golang.org/p/J99_xEbaWo

    0 讨论(0)
提交回复
热议问题