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
Was this helpful?