If append works beyond initial capacity

Hello there, GO is awesome !

→ $go version
→ go version go1.9.2 linux/amd64

→ Environment Variables :slight_smile:

GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/no1/go"
GORACE=""
GOROOT="/usr/local/go"
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build648036717=/tmp/go-build -gno-record-gcc-switches"
CXX="g++"
CGO_ENABLED="1"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"

→ Runnable Program :face_with_raised_eyebrow:

→ Link on Go Play Ground

→ What did you expect to see?
→ I want to know what i’m missing.
→ Is there any other way where i can discuss minor programs than reporting issues after
referring to Documentation/Source Code
→ I’m open for suggestions to help me learn go better (any kind).
→ I thought cap and len would be equal (just before last NOTE print)

→ What did you see instead?
→ A part of Program Output
// …
len : 7 cap : 16
How come the capacity is more than length in this case ?
// …

→ Complete Code :
//
// program.go
// Just started using Go
//

package main

import (
	"fmt"
)

func main() {
	// Check 1 - Understood how to create slice
	fmt.Println("Creating a slice with len 3 and cap 5")
	s := make([]byte, 3, 5)
	fmt.Println("Printing slice information:")
	for in, _ := range s {
		s[in] = byte(in)
		fmt.Println("At index :", in, "byte value :", s[in])
	}
	fmt.Println("len : ", len(s), "cap : ", cap(s))

	// Check 2 - Understood append function syntax
	fmt.Println("Appending 3, 4 as new elements in slice")
	s = append(s, 3, 4)
	fmt.Println("Printing slice information:")
	for in, v := range s {
		fmt.Println(in, v)
	}
	fmt.Println("len : ", len(s), "cap : ", cap(s))

	// Check 3 - Trying to check, if append works beyond initial capacity
	fmt.Println("Appending 5, 6 as new elements in slice to go out of bound")
	s = append(s, 5, 6)
	fmt.Println("Printing slice information:")
	for in, v := range s {
		fmt.Println(in, v)
	}
	fmt.Println("len : ", len(s), "cap : ", cap(s))

	// NOTE :
	fmt.Printf("How come the capacity is more than length in this case ?\n cap function is language built-in so, cann't see the source code or documentation to figure out what's going on here ?\n")
}
2 Likes

In go programming language, when you declare slice for example :- s:=make([]int, 5, 5).
In the above example, a slice “s” is declared using shorthand declaration using “make” with length and capacity as 5. It means that slice “s” is referring to five initial elements of an array and capacity of an array is also 5.
Consider there are five random int elements in array and you want to put 6th element then, you cannot put elements directly into it like for example s[5]=44. This would throw a run-time error saying index out of bounds because slice is referring to only 5 elements since length is declared as 5 and capacity of an array is also five.
However, when append function is used, go takes all the elements of an array creates a new array of size double the capacity and put all the elements into the new array automatically. Now the capacity is 10 and length increases as per elements are stored in the array.

2 Likes

You have stumbled upon a common culprit in go - that slices make your life easier but may not always behave the way you initially thought. :smiley:
I suspect you know that, but it’s important to stress: Slices are NOT Arrays
They may behave similar to how arrays do in JavaScript for example, but they are different from both those and arrays in “traditional” programming languages.
Hence, there are important peculiarities that you need to know and you found one.

as @Tanmay_Shirsath already said:
append() will increase the capacity of a slice if it would go out of bounds (that is one reason it exists instead of slice[len+1]).
It does that by allocating a new array to be the new underlying array of the slice. Since that is a costly operation (allocation + copy + gc of old array) it will make the new array bigger than it needs to be, so that if you do another append(), it won’t have to grow every time.
In fact, I guess that’s the most useful function of the “cap” argument in make (it will work without) - for if you know in advance that you will grow (a lot), so you can avoid allocation. To be honest, I barely use it in my code.

To briefly answer your original question:

No, cap and len do not need to be equal, len just can’t be bigger than cap.
Len is the indicated size of the slice (but slices can “grow”)
Cap is the maximum size the slice can grow to (before needing to swap arrays, which append() does for you)

You can read about this in the docs for Slices (maybe read the entire doc, it’s a great read).
More detail about the slice situation and better than I could ever explain it is in This Blogpost

Also your OP is commendable, great detail (even for such a “minor” issue)! It is refreshing to see so much effort put in a request. :+1:

2 Likes

In my opinion about len and cap in slice, It’s like a bridge that has a capacity of e.g tons and has a length of km. :grin:

1 Like

Thanks @Tanmay_Shirsath

1 Like

Thanks, @Jaytn

1 Like