In what situations would you use `var foo []int` as opposed to `foo := []int{}` or `foo := make([]int, 0)`?

Based on this question (and answer): How should I define an empty slice in Go? - Stack Overflow

I’m wondering why there is a way to declare a slice without an array backing it. I’m also not entirely sure what the difference is between a slice with a value of nil as opposed to a slice with a value, even though all three methods create a slice with len and cap as 0.

I’m struggling to put my confusion into words exactly (which is why I’m so confused!) so I’m hoping to have a discussion on the topic rather than posting to stackoverflow only to get downvoted to oblivion.

Thanks!

1 Like

A slice is essentially a structure with 3 fields:

type slice[T any] struct {
    data *T
    len int
    cap int
}

A nil slice is essentially var a = slice[int]{nil, 0, 0}, but make([]int, 0) will actually fill in the pointer to data with a non-nil pointer. I only see this being useful if your code needs to do something different when slice == nil vs. len(slice) == 0 (maybe JSON serialization?).

My personal convention is:

  1. a := make([]int, 0, capacity) where capacity >= 4 for most situations.

    Currently, make([]int, 0) with the default Go implementation sets the data pointer to a shared 0-size memory location for all types (i.e. multiple calls to make([]int, 0) and even make([]string, 0) will result in a slice whose data pointer is the same, but will vary between executions of the application).

    Go’s append function works by doubling the current slice capacity (until you get to very large capacities, or if the slice capacity is zero, it is increased to 1), so your first 3 appends will result in reallocations (capacity of 0 → 1, 1 → 2, 2 → 4), so if you know the capacity is likely to be more than 4, preset the capacity. Some people call this a micro-optimization, but I call it not being wasteful :man_shrugging:.

  2. var a []int if the initialization depends on a condition, for example:

    var a []int
    if something {
        a = doSomething()
    } else {
        a = doSomethingElse()
    }
    

    People who like their code to be “clean,” will argue that something check should be in its own function:

    a := doSomethingOrSomethingElse(something)
    
    // ....
    
    func doSomethingOrSomethingElse(something bool) {
        if something {
            return doSomething()
        }
        return doSomethingElse()
    }
    

    And if you’re OK with doing that everywhere, then you probably don’t need to use the var a []int style of declaring slices.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.