Slices, Maps

Slices and maps contain pointers to the underlying data so be wary of scenarios when they need to be copied.

Declaring Empty Slices

When declaring an empty slice, prefer

var t []string

over

t := []string{}

The former declares a nil slice value, while the latter is non-nil but zero-length. They are functionally equivalent—their len and cap are both zero—but the nil slice is the preferred style.

Copying Slices and Maps

Keep in mind that users can modify a map or slice you received as an argument if you store a reference to it.

// BAD
func (d *Driver) SetTrips(trips []Trip) {
  d.trips = trips
}

trips := ...
d1.SetTrips(trips)

// Did you mean to modify d1.trips?
trips[0] = ...
// GOOD
func (d *Driver) SetTrips(trips []Trip) {
  d.trips = make([]Trip, len(trips))
  copy(d.trips, trips)
}

trips := ...
d1.SetTrips(trips)

// We can now modify trips[0] without affecting d1.trips.
trips[0] = ...

Returning Slices and Maps

Similarly, be wary of user modifications to maps or slices exposing internal state.

// BAD
type Stats struct {
  mu sync.Mutex
  counters map[string]int
}

// Snapshot returns the current stats.
func (s *Stats) Snapshot() map[string]int {
  s.mu.Lock()
  defer s.mu.Unlock()

  return s.counters
}

// snapshot is no longer protected by the mutex, so any
// access to the snapshot is subject to data races.
snapshot := stats.Snapshot()
// GOOD
type Stats struct {
  mu sync.Mutex
  counters map[string]int
}

func (s *Stats) Snapshot() map[string]int {
  s.mu.Lock()
  defer s.mu.Unlock()

  result := make(map[string]int, len(s.counters))
  for k, v := range s.counters {
    result[k] = v
  }
  return result
}

// Snapshot is now a copy.
snapshot := stats.Snapshot()

Slice

var result []interface{}

// i and value are loop iteration values.
for i, item := range mySlice {

  // Bad
  result = append(result, &item) // NEVER create a pointer to any iteration variable.
  
  // Good
  result = append(result, &mySlice[i]) // Alternative, create a pointer using the index

  // Good (by copying)
  temp := item                   // Alternative, first copy the iteration variable.
  result = append(result, &temp) // Then create a pointer to the copy. 
} 

Map

var result []interface{}

// key and value are loop iteration values.
for key, value := range myMap {

  // Bad
  result = append(result, &value) // NEVER create a pointer to any iteration variable.
  
  // Good
  temp := value                   // Instead, first copy the iteration variable.
  result = append(result, &temp)  // Then create a pointer to the copy. 
} 

Don't create new variables with append

// Bad
notnew := append(old, add) // notnew might not be a new independent slice, but point to the same array as old, so modification to old can affect notnew.  

// Good
copied := append([]byte(nil), old...) // First do a proper copy
copied = append(copied, add)          // Then append to itself

// Ok, but weird and easy to get wrong
copied := append(old[0:0:0], old...)  // Another way to copy: `old[0:0:0]` is empty slice with same type as old. Note not including the third zero is BAD.
copied = append(copied, add)          // Then append to itself

// Ok, but easy to get wrong
copied := make([]byte, len(old))      // Yet another way to copy; make new SAME LENGTH slice 
copy(copied, old)                     // Copy using the copy command (note it copies the minimum length of copied and old!)
copied = append(copied, add)          // Then append to itself

Last updated