ARTICLE AD BOX
When you do
if len(logs) == cap(logs) { logs = logs[int(cap(logs) / 2):] }Go assigns to logs a (newly constructed) slice value which refers to the same underlying (backing) array the original slice value contained in logs before the assignment referred to.
The capacity decreased simply because a slice is "a view" into the backing array, and its capacity defines how much room there is available to "expand" this view towards the end of the array without reallocating it.
So, basically, when len(logs) == cap(logs) there is no free room in the backing array: the last element in logs is located right at the end of the backing array, and so when you subslice logs to contain its "upper" half (that one which is towards the end of the array), you end up with a slice which is a view into that upper half and the original lower half hanging off before the 0th element of the resulting slice.
Let's try to illustrate.
Suppose your slice s and its backing array contain 6 elements:
Now supose you do s = s[3:]. Then s will point at the middle of the original backing array:
+- s now points here | v |_|_|_|_|_|_| — the backing array 0th 5th len == 3 cap == 3Note that the backing array did not go anywhere, and no memory was copied. The three elements before the one s now points at are now inaccessible. (Inaccessible for your own code, as written, and you could still have access to them by doing something like old := s; s = s[3:] — where old would still point to the 0th element of the original backing array.)
Since the capacity of the new slice is equal to its length (so there are 0 spare elements), when you append to it, Go will have to allocate a new backing array with enough room to hold the original 3 elements of the existing slice plus the room needed to accomodate what is being appended (and plus a bit of extra space to amortize reallocations which will happen during the future append calls).
Now a note on garbage collection.
It's tricky to say whether you leak memory with the approach you employ in your code.
From the PoV of the Go runtime, no memory is leaked in the sense there still exist live variable which points at the backing array — not to its starting cell but this does not matter, and the GC will mark this backing array during one of its future scans as garbage when it will detect no live pointers point to it (to simplify: no live slice variables refer to it), and then that memory will eventually be reclaimed.
On the other hand, all the elements which became "hanging off before the beginning of the slice" are inaccessible only to your code, but from the PoV of the GC they are perfectly live as they constitute a part of a single allocated memory chunk which have live references to it.
Hence the GC will neither attempt to somehow "chop off" that memory nor will it consider the values of these inaccessible elements as garbage eligible for reclaiming.
These considerations maybe are something to think through in some scenarios. You might also consider the alternatives:
Sometimes, you might want to better copy the subslice you're interested in in a way to force memory allocation (in your case that'd be something like logs = append([]string(nil), logs[startPoint:]...); Sometimes, you might want to keep the inaccessible memory as is (specifically no not allocate new memory) but would like to make the memory of the inaccessible elements be eligible for reclaiming; typically this means overwriting each of them with the zero value for their type; Sometimes, you might want to just move the memory around in the existing slice — something Peter has shown you how to do (note that copy is explicitly documented as handling copies of overlapping regions; they are slower but result in expected outcome).All-in-all, I would highly recommend to read these articles in the order presented:
https://go.dev/blog/slices-intro https://go.dev/blog/slices https://go.dev/blog/stringsThis will have you completely covered on how slices and backing arrays work, and also strings which are mostly read-only byte slices ;-)
