163

What is the appropriate way to clear a slice in Go?

Here's what I've found in the go forums:

// test.go
package main

import (
    "fmt"
)

func main() {
    letters := []string{"a", "b", "c", "d"}
    fmt.Println(cap(letters))
    fmt.Println(len(letters))
    // clear the slice
    letters = letters[:0]
    fmt.Println(cap(letters))
    fmt.Println(len(letters))
}

Is this correct?

To clarify, the buffer is cleared so it can be reused.

An example is Buffer.Truncate function in the bytes package.

Notice that Reset just calls Truncate(0). So it appears that in this case line 70 would evaluate: b.buf = b.buf[0 : 0]

http://golang.org/src/pkg/bytes/buffer.go

// Truncate discards all but the first n unread bytes from the buffer.
60  // It panics if n is negative or greater than the length of the buffer.
61  func (b *Buffer) Truncate(n int) {
62      b.lastRead = opInvalid
63      switch {
64      case n < 0 || n > b.Len():
65          panic("bytes.Buffer: truncation out of range")
66      case n == 0:
67          // Reuse buffer space.
68          b.off = 0
69      }
70      b.buf = b.buf[0 : b.off+n]
71  }
72  
73  // Reset resets the buffer so it has no content.
74  // b.Reset() is the same as b.Truncate(0).
75  func (b *Buffer) Reset() { b.Truncate(0) }
Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Chris Weber
  • 5,555
  • 8
  • 44
  • 52
  • 1
    A quick test on: http://play.golang.org/p/6Z-qDQtpbg seems to suggest that it will work (won't change the capacity but it will truncate the length) – Jason Sperske Jun 06 '13 at 20:41

5 Answers5

251

Setting the slice to nil is the best way to clear a slice. nil slices in go are perfectly well behaved and setting the slice to nil will release the underlying memory to the garbage collector.

See playground

package main

import (
    "fmt"
)

func dump(letters []string) {
    fmt.Println("letters = ", letters)
    fmt.Println(cap(letters))
    fmt.Println(len(letters))
    for i := range letters {
        fmt.Println(i, letters[i])
    }
}

func main() {
    letters := []string{"a", "b", "c", "d"}
    dump(letters)
    // clear the slice
    letters = nil
    dump(letters)
    // add stuff back to it
    letters = append(letters, "e")
    dump(letters)
}

Prints

letters =  [a b c d]
4
4
0 a
1 b
2 c
3 d
letters =  []
0
0
letters =  [e]
1
1
0 e

Note that slices can easily be aliased so that two slices point to the same underlying memory. The setting to nil will remove that aliasing.

This method changes the capacity to zero though.

Tim S. Van Haren
  • 8,861
  • 2
  • 30
  • 34
Nick Craig-Wood
  • 52,955
  • 12
  • 126
  • 132
  • Nick thanks of the response. Please see my update it you would. I'm clearing the slice for reuse. So I don't necessarily want that the underlying memory released to the GC as I'll just have to allocate it again. – Chris Weber Jun 07 '13 at 15:17
  • it is what I searched!) – Timur Fayzrakhmanov Dec 19 '14 at 20:31
  • 6
    Based on the title "How do you clear a slice in Go?" this is by far and away the safer answer and should be the accepted one. A perfect answer would be the combination of the originally accepted answer and this one so people can decide for themselves, though. – Shadoninja Oct 13 '15 at 20:35
  • 2
    `append`ing to a `nil` slice has always worked in Go? – alediaferia Jan 10 '16 at 16:12
  • @alediaferia ever since go 1.0 certainly. – Nick Craig-Wood Jan 11 '16 at 21:48
  • Is that nil technique only for slices or will it work for maps too? Nil a map, and then reuse it again later by adding/creating new map entry in the now empty map. – David May 17 '18 at 20:06
  • Side note: A: *'[...] will release the underlying memory [...]'* vs. Q: *'[...] so it can be reused'* – if I read the question right, re-allocation, as necessary after setting to nil, should have been avoided? – Aconcagua Sep 17 '20 at 10:48
  • Note: the result of nil slices and empty slices will be different in JSON marshaling – Amin Shojaei Mar 27 '22 at 07:27
152

It all depends on what is your definition of 'clear'. One of the valid ones certainly is:

slice = slice[:0]

But there's a catch. If slice elements are of type T:

var slice []T

then enforcing len(slice) to be zero, by the above "trick", doesn't make any element of

slice[:cap(slice)]

eligible for garbage collection. This might be the optimal approach in some scenarios. But it might also be a cause of "memory leaks" - memory not used, but potentially reachable (after re-slicing of 'slice') and thus not garbage "collectable".

zzzz
  • 87,403
  • 16
  • 175
  • 139
  • 2
    Interesting. Is there any other way to remove all elements from the underlying array of the slice while leaving the underlying capacity unchanged? – Chris Weber Jun 06 '13 at 21:07
  • 3
    @ChrisWeber: just iterate over the underlying array and set all the elements to a new value – newacct Jun 07 '13 at 14:02
  • 3
    @jnml, I do want to reuse the slice (and the underlying array storage) so I'm not constantly allocating a new slice (with array). I've edited my question to clarify and to show some example code from the standard library. – Chris Weber Jun 07 '13 at 15:19
  • 1
    I'm new to Go. Could you please explain more on why this can be an optimal approach please? Thanks in advance. – satoru May 12 '16 at 06:14
  • Are you sure that the slice size reset cause memory leaks? I am not able to reproduce it – Tommaso Barbugli Apr 13 '18 at 14:54
  • 1
    To clarify a few basic points here, 1. Unlike assigning to nil this does not require garbage collecting/reallocating the memory at all thus depends on the use case it can be faster; 2. It's not a real memory leak in the sense that you can still free it (by assigning it to nil), but if you don't use it properly the program will consume the memory while you don't really need it – user202729 May 17 '22 at 09:58
6

I was looking into this issue a bit for my own purposes; I had a slice of structs (including some pointers) and I wanted to make sure I got it right; ended up on this thread, and wanted to share my results.

To practice, I did a little go playground: https://play.golang.org/p/9i4gPx3lnY

which evals to this:

package main

import "fmt"

type Blah struct {
    babyKitten int
    kittenSays *string
}

func main() {
    meow := "meow"
    Blahs := []Blah{}
    fmt.Printf("Blahs: %v\n", Blahs)
    Blahs = append(Blahs, Blah{1, &meow})
    fmt.Printf("Blahs: %v\n", Blahs)
    Blahs = append(Blahs, Blah{2, &meow})
    fmt.Printf("Blahs: %v\n", Blahs)
    //fmt.Printf("kittenSays: %v\n", *Blahs[0].kittenSays)
    Blahs = nil
    meow2 := "nyan"
    fmt.Printf("Blahs: %v\n", Blahs)
    Blahs = append(Blahs, Blah{1, &meow2})
    fmt.Printf("Blahs: %v\n", Blahs)
    fmt.Printf("kittenSays: %v\n", *Blahs[0].kittenSays)
}

Running that code as-is will show the same memory address for both "meow" and "meow2" variables as being the same:

Blahs: []
Blahs: [{1 0x1030e0c0}]
Blahs: [{1 0x1030e0c0} {2 0x1030e0c0}]
Blahs: []
Blahs: [{1 0x1030e0f0}]
kittenSays: nyan

which I think confirms that the struct is garbage collected. Oddly enough, uncommenting the commented print line, will yield different memory addresses for the meows:

Blahs: []
Blahs: [{1 0x1030e0c0}]
Blahs: [{1 0x1030e0c0} {2 0x1030e0c0}]
kittenSays: meow
Blahs: []
Blahs: [{1 0x1030e0f8}]
kittenSays: nyan

I think this may be due to the print being deferred in some way (?), but interesting illustration of some memory mgmt behavior, and one more vote for:

[]MyStruct = nil
max garvey
  • 85
  • 1
  • 2
  • Nice detailed examples. Thanks! – Dolanor Aug 07 '17 at 01:40
  • 3
    This does not show the memory addresses of meo1 and meow2 being the same: `0x1030e0c0` is not equal to `0x1030e0f0` (the former ends in `c0`, the latter in `f0`). – carbocation Sep 25 '18 at 13:15
  • Got to agree with @carbocation here, those memory addresses are not the same. I don't claim to be able to better explain what's going on here, but this doesn't serve as evidence for me. I do see the same 8-byte discrepancy in the addresses of `meow2` each run... – rbrtl Apr 01 '19 at 13:02
6

Go 1.21 (Q3 2023) will propose a new builtin keyword: clear()

issue 56351 includes the following documentation:

The built-in function clear takes an argument of map, slice or type parameter type, and deletes or zeroes out all elements.

Call Argument type Result >
clear(m) map[K]T deletes all entries, resulting in an empty map (len(m) == 0)
clear(s) []T sets all elements up to the length of s to the zero value of T
clear(t) type parameter see below >

If the argument type is a type parameter, all types in its type set must be maps or slices, and clear performs the operation corresponding to the actual type argument.

If the map or slice is nil, clear is a no-op.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
0

2023 Update

With Go 1.21 you can use clear function:

https://tip.golang.org/ref/spec#Clear

Tom Smykowski
  • 25,487
  • 54
  • 159
  • 236
  • Why repeating what I reported [2 months ago](https://stackoverflow.com/a/76349064/6309)? – VonC Jul 31 '23 at 11:42