Unexpected result when removing an element from a slice - go

I encountered unexpected results when I remove the first element from slice, this is my test code, hope to help you understand my confuse
define structure
type A struct {
member int
}
func (a A) String() string {
return fmt.Sprintf("%v", a.member)
}
type B struct {
a A
aPoint *A
}
func (b B) String() string {
return fmt.Sprintf("a: %v, aPoint: %v", b.a, b.aPoint)
}
below is my test case
func TestSliceRemoveFirstEle(t *testing.T) {
demo := []B{
{a: A{member: 1}},
{a: A{member: 2}},
{a: A{member: 3}},
}
for i := range demo {
demo[i].aPoint = &demo[i].a
}
fmt.Println("demo before operation is ", demo) // result: demo before operation is [a: 1, aPoint: 1 a: 2, aPoint:2 a: 3, aPoint: 3]
demo = append(demo[:0], demo[1:]...)
fmt.Println("demo after operation is ", demo) // result: demo after operation is [a: 2, aPoint: 3 a: 3, aPoint: 3]
}
my expected result is [a: 2, aPoint: 2 a: 3, aPoint: 3]
when I use another way adding element to the slice, it work well.
func TestAddSliceRemoveFirstEle(t *testing.T) {
demo := make([]B, 0, 3)
a1 := A{member: 1}
a2 := A{member: 2}
a3 := A{member: 3}
demo = append(demo, B{a: a1, aPoint: &a1}, B{a: a2, aPoint: &a2}, B{a: a3, aPoint: &a3})
fmt.Println("demo before operation is ", demo) // result: demo before operation is [a: 1, aPoint: 1 a: 2, aPoint: 2 a: 3, aPoint: 3]
demo = append(demo[:0], demo[1:]...)
fmt.Println("demo after operation is ", demo) // result: demo after operation is [a: 2, aPoint: 2 a: 3, aPoint: 3]
}
I'm confused about the result, what happened in the first case after removing element from slice, and what's the difference between these two implementations?

In your first example all the A structs exist only in the slice and the pointer points to elements of the slice.
In your second example, the A structs exist outside the slice and so there are no pointers against the elements of the slice.
As per the golang append specification at https://golang.org/ref/spec#Appending_and_copying_slices
If the capacity of s is not large enough to fit the additional values, append allocates a new, sufficiently large underlying array that fits both the existing slice elements and the additional values. Otherwise, append re-uses the underlying array.
Since we are decreasing the size the underlying array is big enough so we are reusing it.
This is what the underlying array of capacity 3 looks like before and after the reslicing
123 - before
233 - after
What pointed at '2' before is now pointing at '3' as your example shows, a simpler way to see can be through this playground
package main
import (
"fmt"
)
func main() {
mySlice := []int{1, 2, 3}
one := &mySlice[0]
two := &mySlice[1]
three := &mySlice[2]
fmt.Printf("%v-%d\n%v-%d\n%v-%d\n",one,*one,two,*two,three,*three)
fmt.Printf("%v cap %d\n",mySlice, cap(mySlice))
mySlice = append(mySlice[:1],mySlice[2:]...)
fmt.Printf("%v cap %d\n",mySlice, cap(mySlice))
fmt.Printf("%v-%d\n%v-%d\n%v-%d\n",one,*one,two,*two,three,*three)
}
Where you'll see
0xc0000be000-1
0xc0000be008-2
0xc0000be010-3
[1 2 3] cap 3
[1 3] cap 3
0xc0000be000-1
0xc0000be008-3
0xc0000be010-3
In short, taking pointers out of elements of a slice and reslicing it will always cause these kind of errors.
It could also cause memory leak issues as per the footnote here https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order

Essentially the aPoint pointers still point to the same address, but the values at those addresses changed.
After the append it is demo[0].aPoint == &demo[1].a and not demo[0].aPoint == &demo[0].a.
https://play.golang.org/p/HoNhFlxTEFN
The slice expression demo[:0] will result in a new slice of length 0 that points to the same underlying array as the demo slice. The elements stored in that array are still there, they are not discarded.
So after demo[:0] the underlying array is still:
[
B{a:1,<pointer_to_idx:0_field_a>},
B{a:2,<pointer_to_idx:1_field_a>},
B{a:3,<pointer_to_idx:2_field_a>},
]
demo[1:]... drops the first element and passes the remaining two elements to append. Then append takes those two elements and updates the underlying array's first two elements. After that the underlying array will look like this:
[
B{a:2,<pointer_to_idx:1_field_a>},
B{a:3,<pointer_to_idx:2_field_a>},
B{a:3,<pointer_to_idx:2_field_a>},
]
Notice that now, the aPointer field of the 0th element of the array points to the 1st element's a field. And the aPointer field of the 1st element points to the 2nd element's a field.

Related

Why does the keyword range in golang work for 2d-slices?

I'd like to make a copy of a slice of bytes, convert the type from [][4]byte to [][]byte. And I expect the followering code to return [[1 2 3 4] [2 3 4 5]], but actually got [[2 3 4 5] [2 3 4 5]].
package main
import "fmt"
func main() {
ids := [][4]byte{[4]byte{1, 2, 3, 4}, [4]byte{2, 3, 4, 5}}
var slices [][]byte
for _, id := range ids {
slices = append(slices, id[:])
}
fmt.Println(slices)
}
Why?
Why appending id[:] to the slice would overwrite the first element in the slice?
As said by Marc, you are appending a reference via id.
Therefore when you iterate, and id changes its value, your "slices" to which you are appending are getting re-evaluated with id new values.
To avoid this:
for i := range ids {
slices = append(slices, ids[i][:]) // here you are referring to the value at index i of ids
}

In Go, how do I duplicate the last element of a slice?

If I have a Python list, it is simple to duplicate the last element: l.append(l[-1]). Is there an elegant way to do this with Go slices?
The best I have so far is data = append(data, data[len(data) - 1]).
That is the proper way to do it.
In Go you can't use negative indices, so the index of the last element is len(data) -1.
And in Go append() is a builtin function and not a method of slices, and it returns a new slice value which you have to assign or store if you need the extended slice, so there's nothing you can make shorter in your code in general.
Note that if you were on to create your own type, you could attach methods to it which could simpify things. For example:
type ints []int
func (i *ints) append(es ...int) {
*i = append(*i, es...)
}
func (i *ints) appendLast() {
*i = append(*i, (*i)[len(*i)-1])
}
Using it:
is := ints{1, 2, 3}
fmt.Println(is)
is.append(4, 5) // Append some elements
fmt.Println(is)
is.append(is[len(is)-1]) // Append last element explicitly
fmt.Println(is)
is.appendLast() // Append last element using appendLast method
fmt.Println(is)
// You can also use it for []int values, with conversion:
is2 := []int{1, 2, 3}
(*ints)(&is2).appendLast()
fmt.Println(is2)
Output (try it on the Go Playground):
[1 2 3]
[1 2 3 4 5]
[1 2 3 4 5 5]
[1 2 3 4 5 5 5]
[1 2 3 3]
You can also use simple util functions without creating a new type:
func appendLast(i []int) []int {
return append(i, i[len(i)-1])
}
func appendLast2(i *[]int) {
*i = append(*i, (*i)[len(*i)-1])
}
func main() {
is := []int{1, 2, 3}
fmt.Println(is)
is = appendLast(is)
fmt.Println(is)
appendLast2(&is)
fmt.Println(is)
}
Output (try it on the Go Playground):
[1 2 3]
[1 2 3 3]
[1 2 3 3 3]
data = append(data, data[len(data) - 1])
Yes. It's the best way to do in Go. It's not possible to use a negative index in Go.

How to append a new element to a slice?

I tried to append a new element to a slice, just like appending a new element to a list in Python, but there is an error
package main
import "fmt"
func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)
s[len(s)] = 100
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("%v\n", s)
}
and the output is
[2 3 5 7 11 13]
panic: runtime error: index out of range
How can I append a new element to a slice then?
Is it correct that slice in Go is the closet data type to list in Python?
Thanks.
Appending in Go uses the builtin method append. For example:
s := []byte{1, 2, 3}
s = append(s, 4, 5, 6)
fmt.Println(s)
// Output: [1 2 3 4 5 6]
You are correct, slices in Go are close to Python's list, and, in fact, many of the same indexing and slicing operations work on them.
When you do:
s[len(s)] = 100
it panics because you cannot access a value beyond the length of the slice. There appears to be some confusion here, because in Python this works the exact same way. Attempting the above in Python (unless you've already expanded the list) will give:
IndexError: list assignment index out of range

Golang: appending slices with or w/o allocation

Go's append() function only allocates new slice data, when the capacity of the given slice is not sufficient (see also: https://stackoverflow.com/a/28143457/802833). This can lead to unexpected behavior (at least for me as a golang newbie):
package main
import (
"fmt"
)
func main() {
a1 := make([][]int, 3)
a2 := make([][]int, 3)
b := [][]int{{1, 1, 1}, {2, 2, 2}, {3, 3, 3}}
common1 := make([]int, 0)
common2 := make([]int, 0, 12) // provide sufficient capacity
common1 = append(common1, []int{10, 20}...)
common2 = append(common2, []int{10, 20}...)
idx := 0
for _, k := range b {
a1[idx] = append(common1, k...) // new slice is allocated
a2[idx] = append(common2, k...) // no allocation
idx++
}
fmt.Println(a1)
fmt.Println(a2) // surprise!!!
}
output:
[[10 20 1 1 1] [10 20 2 2 2] [10 20 3 3 3]]
[[10 20 3 3 3] [10 20 3 3 3] [10 20 3 3 3]]
https://play.golang.org/p/8PEqFxAsMt
So, what ist the (idomatic) way in Go to force allocation of new slice data or more precisely to make sure that the slice argument to append() remains unchanged?
You might maintain a wrong idea of how slices work in Go.
When you append elements to a slice, the call to append() returns a new slice. If reallocation did not happen, both slice values — the one you called append() on and the one it returned back — share the same backing array but they will have different lengths; observe:
package main
import "fmt"
func main() {
a := make([]int, 0, 10)
b := append(a, 1, 2, 3)
c := append(a, 4, 3, 2)
fmt.Printf("a=%#v\nb=%#v\nc=%#v\n", a, b, c)
}
outputs:
a=[]int{}
b=[]int{4, 3, 2}
c=[]int{4, 3, 2}
So, len(a) == 0, len(b) == 3, len(c) == 3, and the second call to append() owerwrote what the first one did because all the slices share the same underlying array.
As to reallocation of the backing array, the spec is clear:
If the capacity of s is not large enough to fit the additional values, append allocates a new, sufficiently large underlying array that fits both the existing slice elements and the additional values. Otherwise, append re-uses the underlying array.
From this, it follows that:
append() never copies the underlying storage if the capacity of the slice being appeneded to is sufficient.
If there's not enough capacity, the array will be reallocated.
That is, given a slice s to which you want to append N elements, the reallocation won't be done iff cap(s) - len(s) ≥ N.
Hence I suspect your problem is not about unexpected reallocation results but rather about the concept of slices as implemented in Go. The code idea to absorb is that append() returns the resulting slice value, which you're supposed to be using after the call unless you fully understand the repercussions.
I recommend starting with this to fully understand them.
Thanx for your feedback.
So the solution to gain control of the memory allocation is to do it explicitely (which remembers me that Go is a more a system language than other (scripting) langs):
package main
import (
"fmt"
)
func main() {
a1 := make([][]int, 3)
a2 := make([][]int, 3)
b := [][]int{{1, 1, 1}, {2, 2, 2}, {3, 3, 3}}
common1 := make([]int, 0)
common2 := make([]int, 0, 12) // provide sufficient capacity
common1 = append(common1, []int{10, 20}...)
common2 = append(common2, []int{10, 20}...)
idx := 0
for _, k := range b {
a1[idx] = append(common1, k...) // new slice is allocated
a2[idx] = make([]int, len(common2), len(common2)+len(k))
copy(a2[idx], common2) // copy & append could probably be
a2[idx] = append(a2[idx], k...) // combined into a single copy step
idx++
}
fmt.Println(a1)
fmt.Println(a2)
}
output:
[[10 20 1 1 1] [10 20 2 2 2] [10 20 3 3 3]]
[[10 20 1 1 1] [10 20 2 2 2] [10 20 3 3 3]]
https://play.golang.org/p/Id_wSZwb84

Concatenate two slices in Go

I'm trying to combine the slice [1, 2] and the slice [3, 4]. How can I do this in Go?
I tried:
append([]int{1,2}, []int{3,4})
but got:
cannot use []int literal (type []int) as type int in append
However, the documentation seems to indicate this is possible, what am I missing?
slice = append(slice, anotherSlice...)
Add dots after the second slice:
// vvv
append([]int{1,2}, []int{3,4}...)
This is just like any other variadic function.
func foo(is ...int) {
for i := 0; i < len(is); i++ {
fmt.Println(is[i])
}
}
func main() {
foo([]int{9,8,7,6,5}...)
}
Appending to and copying slices
The variadic function append appends zero or more values x to s
of type S, which must be a slice type, and returns the resulting
slice, also of type S. The values x are passed to a parameter of
type ...T where T is the element type of S and the respective
parameter passing rules apply. As a special case, append also accepts
a first argument assignable to type []byte with a second argument of
string type followed by .... This form appends the bytes of the
string.
append(s S, x ...T) S // T is the element type of S
s0 := []int{0, 0}
s1 := append(s0, 2) // append a single element s1 == []int{0, 0, 2}
s2 := append(s1, 3, 5, 7) // append multiple elements s2 == []int{0, 0, 2, 3, 5, 7}
s3 := append(s2, s0...) // append a slice s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}
Passing arguments to ... parameters
If f is variadic with final parameter type ...T, then within the
function the argument is equivalent to a parameter of type []T. At
each call of f, the argument passed to the final parameter is a new
slice of type []T whose successive elements are the actual arguments,
which all must be assignable to the type T. The length of the slice is
therefore the number of arguments bound to the final parameter and may
differ for each call site.
The answer to your question is example s3 := append(s2, s0...) in the Go Programming Language Specification. For example,
s := append([]int{1, 2}, []int{3, 4}...)
Nothing against the other answers, but I found the brief explanation in the docs more easily understandable than the examples in them:
func append
func append(slice []Type, elems ...Type) []Type The append built-in
function appends elements to the end of a slice. If it has sufficient
capacity, the destination is resliced to accommodate the new elements.
If it does not, a new underlying array will be allocated. Append
returns the updated slice. It is therefore necessary to store the
result of append, often in the variable holding the slice itself:
slice = append(slice, elem1, elem2)
slice = append(slice, anotherSlice...)
As a special case, it is legal to append a string to a byte slice,
like this:
slice = append([]byte("hello "), "world"...)
I would like to emphasize #icza answer and simplify it a bit since it is a crucial concept. I assume that reader is familiar with slices.
c := append(a, b...)
This is a valid answer to the question.
BUT if you need to use slices 'a' and 'c' later in code in different context, this is not the safe way to concatenate slices.
To explain, lets read the expression not in terms of slices, but in terms of underlying arrays:
"Take (underlying) array of 'a' and append elements from array 'b' to
it. If array 'a' has enough capacity to include all elements from 'b'
- underlying array of 'c' will not be a new array, it will actually be array 'a'. Basically, slice 'a' will show len(a) elements of
underlying array 'a', and slice 'c' will show len(c) of array 'a'."
append() does not necessarily create a new array! This can lead to unexpected results. See Go Playground example.
Always use make() function if you want to make sure that new array is allocated for the slice. For example here are few ugly but efficient enough options for the task.
la := len(a)
c := make([]int, la, la + len(b))
_ = copy(c, a)
c = append(c, b...)
la := len(a)
c := make([]int, la + len(b))
_ = copy(c, a)
_ = copy(c[la:], b)
I think it's important to point out and to know that if the destination slice (the slice you append to) has sufficient capacity, the append will happen "in-place", by reslicing the destination (reslicing to increase its length in order to be able to accommodate the appendable elements).
This means that if the destination was created by slicing a bigger array or slice which has additional elements beyond the length of the resulting slice, they may get overwritten.
To demonstrate, see this example:
a := [10]int{1, 2}
fmt.Printf("a: %v\n", a)
x, y := a[:2], []int{3, 4}
fmt.Printf("x: %v, y: %v\n", x, y)
fmt.Printf("cap(x): %v\n", cap(x))
x = append(x, y...)
fmt.Printf("x: %v\n", x)
fmt.Printf("a: %v\n", a)
Output (try it on the Go Playground):
a: [1 2 0 0 0 0 0 0 0 0]
x: [1 2], y: [3 4]
cap(x): 10
x: [1 2 3 4]
a: [1 2 3 4 0 0 0 0 0 0]
We created a "backing" array a with length 10. Then we create the x destination slice by slicing this a array, y slice is created using the composite literal []int{3, 4}. Now when we append y to x, the result is the expected [1 2 3 4], but what may be surprising is that the backing array a also changed, because capacity of x is 10 which is sufficient to append y to it, so x is resliced which will also use the same a backing array, and append() will copy elements of y into there.
If you want to avoid this, you may use a full slice expression which has the form
a[low : high : max]
which constructs a slice and also controls the resulting slice's capacity by setting it to max - low.
See the modified example (the only difference is that we create x like this: x = a[:2:2]:
a := [10]int{1, 2}
fmt.Printf("a: %v\n", a)
x, y := a[:2:2], []int{3, 4}
fmt.Printf("x: %v, y: %v\n", x, y)
fmt.Printf("cap(x): %v\n", cap(x))
x = append(x, y...)
fmt.Printf("x: %v\n", x)
fmt.Printf("a: %v\n", a)
Output (try it on the Go Playground)
a: [1 2 0 0 0 0 0 0 0 0]
x: [1 2], y: [3 4]
cap(x): 2
x: [1 2 3 4]
a: [1 2 0 0 0 0 0 0 0 0]
As you can see, we get the same x result but the backing array a did not change, because capacity of x was "only" 2 (thanks to the full slice expression a[:2:2]). So to do the append, a new backing array is allocated that can store the elements of both x and y, which is distinct from a.
append( ) function and spread operator
Two slices can be concatenated using append method in the standard golang library. Which is similar to the variadic function operation. So we need to use ...
package main
import (
"fmt"
)
func main() {
x := []int{1, 2, 3}
y := []int{4, 5, 6}
z := append([]int{}, append(x, y...)...)
fmt.Println(z)
}
output of the above code is: [1 2 3 4 5 6]
To concatenate two slices,
func main() {
s1 := []int{1, 2, 3}
s2 := []int{99, 100}
s1 = append(s1, s2...)
fmt.Println(s1) // [1 2 3 99 100]
}
To append a single value to a slice
func main() {
s1 := []int{1,2,3}
s1 := append(s1, 4)
fmt.Println(s1) // [1 2 3 4]
}
To append multiple values to a slice
func main() {
s1 := []int{1,2,3}
s1 = append(s1, 4, 5)
fmt.Println(s1) // [1 2 3 4]
}
Seems like a perfect use for generics (if using 1.18 or later).
func concat[T any](first []T, second []T) []T {
n := len(first);
return append(first[:n:n], second...);
}
append([]int{1,2}, []int{3,4}...) will work. Passing arguments to ... parameters.
If f is variadic with a final parameter p of type ...T, then within f the type of p is equivalent to type []T.
If f is invoked with no actual arguments for p, the value passed to p is nil.
Otherwise, the value passed is a new slice of type []T with a new underlying array whose successive elements are the actual arguments, which all must be assignable to T. The length and capacity of the slice is therefore the number of arguments bound to p and may differ for each call site.
Given the function and calls
func Greeting(prefix string, who ...string)
Greeting("nobody")
Greeting("hello:", "Joe", "Anna", "Eileen")

Resources