Declare slice or make slice?
Asked Answered
R

5

156

In Go, what is the difference between var s []int and s := make([]int, 0)?

I find that both works, but which one is better?

Rondi answered 28/8, 2014 at 7:51 Comment(1)
The first one creates a nil slice, whereas the second creates an empty slice (this is the terminology used by the "Go in action book"). To avoid posting the same answer here too, you can check https://mcmap.net/q/86042/-correct-way-to-initialize-empty-sliceCheese
C
112

In addition to fabriziom's answer, you can see more examples at "Go Slices: usage and internals", where a use for []int is mentioned:

Since the zero value of a slice (nil) acts like a zero-length slice, you can declare a slice variable and then append to it in a loop:

// Filter returns a new slice holding only
// the elements of s that satisfy f()
func Filter(s []int, fn func(int) bool) []int {
    var p []int // == nil
    for _, v := range s {
        if fn(v) {
            p = append(p, v)
        }
    }
    return p
}

It means that, to append to a slice, you don't have to allocate memory first: the nil slice p int[] is enough as a slice to add to.

Cleaning answered 28/8, 2014 at 8:21 Comment(3)
Why you think it would do an allocation? Cap is zero so nothing is allocated with or without make.Derinna
@ArmanOrdookhani Agreed. I just find the declaration var p []int easier than using make (which I associate more with allocation, even though with a 0 cap, it would not allocate anything). In term of readability, I prefer not using make here.Cleaning
I'm more toward using literals everywhere (e.g. p := []int{}). Since we usually use := syntax to declare most variables, it's more natural to have it everywhere instead of having an exceptions for slices. Other than this trying to think of allocations usually push people toward premature optimizations.Derinna
W
163

Simple declaration

var s []int

does not allocate memory and s points to nil, while

s := make([]int, 0)

allocates memory and s points to memory to a slice with 0 elements.

Usually, the first one is more idiomatic if you don't know the exact size of your use case.

Wheelhorse answered 28/8, 2014 at 7:55 Comment(10)
Can i say the same for map? var m map[string]int vs m:= make(map[string]int) ? Thanks.Doenitz
Nah, you need to make maps, because even an empty map needs space allocated for some bookkeeping.Mitten
If you need to return a slice with 0 elements (instead of 'nil'), make is the correct usage.Labrum
The other alternative for map is to declare an empty map literal, which is functionally the same as make(..) except that there is no option to specify the initial size. play.golang.org/p/gn1kVAfZcSAlphaalphabet
If you're building an API and return an array as the response, using the declarative form will return nil in case your slice doesn't have any element, rather than an empty array. However, if make is used to create the slice, an empty array will be returned instead, which is generally the desired effect.Coricoriaceous
As mentioned in a comment on this answer: https://mcmap.net/q/86042/-correct-way-to-initialize-empty-slice, there are differences when attempting to do things like json marshaling. Marshaling the nil slice (var s []int) will produce null, while marshaling the empty slice (s := make([]int, 0)) will produce the expected []Dilworth
Does anyone know why make is not applied automatically? We always need memory and point to it to use the variable. So why is make part of the language at all?Portend
May I ask what is this one: s := []float64{5, 6, 7}, I use it a lot yet doesn't quite understand it.Laryngitis
i find the latter way is quite useful when api want to response empty array as defaultBlowpipe
make won't allocate if len is zero, there is no concept of allocating zero bytes. Pointers to zero sized things will have a shared specific address in Go.Derinna
C
112

In addition to fabriziom's answer, you can see more examples at "Go Slices: usage and internals", where a use for []int is mentioned:

Since the zero value of a slice (nil) acts like a zero-length slice, you can declare a slice variable and then append to it in a loop:

// Filter returns a new slice holding only
// the elements of s that satisfy f()
func Filter(s []int, fn func(int) bool) []int {
    var p []int // == nil
    for _, v := range s {
        if fn(v) {
            p = append(p, v)
        }
    }
    return p
}

It means that, to append to a slice, you don't have to allocate memory first: the nil slice p int[] is enough as a slice to add to.

Cleaning answered 28/8, 2014 at 8:21 Comment(3)
Why you think it would do an allocation? Cap is zero so nothing is allocated with or without make.Derinna
@ArmanOrdookhani Agreed. I just find the declaration var p []int easier than using make (which I associate more with allocation, even though with a 0 cap, it would not allocate anything). In term of readability, I prefer not using make here.Cleaning
I'm more toward using literals everywhere (e.g. p := []int{}). Since we usually use := syntax to declare most variables, it's more natural to have it everywhere instead of having an exceptions for slices. Other than this trying to think of allocations usually push people toward premature optimizations.Derinna
L
25

Just found a difference. If you use

var list []MyObjects

and then you encode the output as JSON, you get null.

list := make([]MyObjects, 0)

results in [] as expected.

Lobe answered 9/3, 2019 at 23:15 Comment(1)
yah, the latter is quite useful when we want to response with [] array instead of nullBlowpipe
A
8

A bit more complete example (one more argument in .make()):

slice := make([]int, 2, 5)
fmt.Printf("length:  %d - capacity %d - content:  %d", len(slice), cap(slice), slice)

Out:

length:  2 - capacity 5 - content:  [0 0]

Or with a dynamic type of slice:

slice := make([]interface{}, 2, 5)
fmt.Printf("length:  %d - capacity %d - content:  %d", len(slice), cap(slice), slice)

Out:

length:  2 - capacity 5 - content:  [<nil> <nil>]
Alsatia answered 24/12, 2018 at 8:26 Comment(0)
B
0

Slice can grow beyond its capacity and this means memory re-allocation which may be costly (it takes some time). There is a rule on how slices will grow:

Go slices grow by doubling until size 1024, after which they grow by 25% each time

Now let's compare two approaches:

  • var p []int
  • p := make([]int, 0, len(s))

Below we have two functions, one with only declaration of the slice and other with slice created using make and with its capacity set.

func FilterSliceNil(s []int, fn func(int) bool) []int {
    var p []int // == nil
    for _, v := range s {
        if fn(v) {
            p = append(p, v)
        }
    }
    return p
}

func FilterSliceCapSet(s []int, fn func(int) bool) []int {
    p := make([]int, 0, len(s)) // Set length=0 and capacity to length of s
    for _, v := range s {
        if fn(v) {
            p = append(p, v)
        }
    }
    return p
}

Benchmark test functions:

package main

import (
    "testing"
)

var slice = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var f = func(i int) bool {
    return i%2 == 0
}

func Benchmark_FilterSliceNil(b *testing.B) {
    for i := 0; i < b.N; i++ {
        FilterSliceNil(slice, f)
    }
}

func Benchmark_FilterSliceCapSet(b *testing.B) {
    for i := 0; i < b.N; i++ {
        FilterSliceCapSet(slice, f)
    }
}

Benchmark results:

$ go test -bench=. -benchtime=10s
goos: linux
goarch: amd64
pkg: example.com/Shadowing
cpu: 13th Gen Intel(R) Core(TM) i7-13700K
Benchmark_FilterSliceNil-24             157664690               76.96 ns/op
Benchmark_FilterSliceCapSet-24          330690351               36.27 ns/op
PASS
ok      example.com/Filter   35.479s

On my machine the version with make takes half the time to complete.

Blanka answered 5/9, 2023 at 12:45 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.