go-data-structures

📁 cxuu/golang-skills 📅 Jan 27, 2026
62
总安装量
15
周安装量
#6565
全站排名
安装命令
npx skills add https://github.com/cxuu/golang-skills --skill go-data-structures

Agent 安装分布

github-copilot 11
gemini-cli 10
codex 10
claude-code 9
kimi-cli 9
amp 9

Skill 文档

Go Data Structures

Source: Effective Go

This skill covers Go’s built-in data structures and allocation primitives.


Allocation: new vs make

Go has two allocation primitives: new and make. They do different things.

new

new(T) allocates zeroed storage for a new item of type T and returns *T:

p := new(SyncedBuffer)  // type *SyncedBuffer, zeroed
var v SyncedBuffer      // type  SyncedBuffer, zeroed

Zero-value design: Design data structures so the zero value is useful without further initialization. Examples: bytes.Buffer, sync.Mutex.

type SyncedBuffer struct {
    lock    sync.Mutex
    buffer  bytes.Buffer
}
// Ready to use immediately upon allocation

make

make(T, args) creates slices, maps, and channels only. It returns an initialized (not zeroed) value of type T (not *T):

make([]int, 10, 100)  // slice: length 10, capacity 100
make(map[string]int)  // map: ready to use
make(chan int)        // channel: ready to use

The Difference

var p *[]int = new([]int)       // *p == nil; rarely useful
var v  []int = make([]int, 100) // v is a usable slice of 100 ints

// Idiomatic:
v := make([]int, 100)

Rule: make applies only to maps, slices, and channels and does not return a pointer.


Composite Literals

Create and initialize structs, arrays, slices, and maps in one expression:

// Struct with positional fields
f := File{fd, name, nil, 0}

// Struct with named fields (order doesn't matter, missing = zero)
f := &File{fd: fd, name: name}

// Zero value
f := &File{}  // equivalent to new(File)

// Arrays, slices, maps
a := [...]string{Enone: "no error", Eio: "Eio", Einval: "invalid"}
s := []string{Enone: "no error", Eio: "Eio", Einval: "invalid"}
m := map[int]string{Enone: "no error", Eio: "Eio", Einval: "invalid"}

Note: It’s safe to return the address of a local variable in Go—the storage survives after the function returns.


Arrays

Arrays are values in Go (unlike C):

  • Assigning one array to another copies all elements
  • Passing an array to a function passes a copy, not a pointer
  • The size is part of the type: [10]int and [20]int are distinct
func Sum(a *[3]float64) (sum float64) {
    for _, v := range *a {
        sum += v
    }
    return
}

array := [...]float64{7.0, 8.5, 9.1}
x := Sum(&array)  // Pass pointer for efficiency

Recommendation: Use slices instead of arrays in most cases.


Slices

Slices wrap arrays to provide a flexible, powerful interface to sequences.

Slice Basics

Slices hold references to an underlying array. Assigning one slice to another makes both refer to the same array:

func (f *File) Read(buf []byte) (n int, err error)

// Read into first 32 bytes of larger buffer
n, err := f.Read(buf[0:32])

Length and Capacity

  • len(s): current length
  • cap(s): maximum length (from start of slice to end of underlying array)

The append Function

func append(slice []T, elements ...T) []T

Always assign the result—the underlying array may change:

x := []int{1, 2, 3}
x = append(x, 4, 5, 6)

// Append a slice to a slice
y := []int{4, 5, 6}
x = append(x, y...)  // Note the ...

Two-Dimensional Slices

Method 1: Independent inner slices (can grow/shrink independently):

picture := make([][]uint8, YSize)
for i := range picture {
    picture[i] = make([]uint8, XSize)
}

Method 2: Single allocation (more efficient for fixed sizes):

picture := make([][]uint8, YSize)
pixels := make([]uint8, XSize*YSize)
for i := range picture {
    picture[i], pixels = pixels[:XSize], pixels[XSize:]
}

For detailed slice internals, see references/SLICES.md.

Declaring Empty Slices

Normative: This is required per Go Wiki CodeReviewComments.

When declaring an empty slice, prefer:

var t []string

over:

t := []string{}

The former declares a nil slice, 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.

Exception for JSON encoding: A nil slice encodes to null, while an empty slice []string{} encodes to []. Use non-nil when you need a JSON array:

// nil slice → JSON null
var tags []string
json.Marshal(tags)  // "null"

// empty slice → JSON array
tags := []string{}
json.Marshal(tags)  // "[]"

Interface design: When designing interfaces, avoid making a distinction between a nil slice and a non-nil zero-length slice, as this can lead to subtle programming errors.


Maps

Maps associate keys with values. Keys must support equality (==).

Creating and Using Maps

var timeZone = map[string]int{
    "UTC":  0*60*60,
    "EST": -5*60*60,
    "CST": -6*60*60,
}

offset := timeZone["EST"]  // -18000

Testing for Presence

An absent key returns the zero value. Use the “comma ok” idiom to distinguish:

seconds, ok := timeZone[tz]
if !ok {
    log.Println("unknown time zone:", tz)
}

// Or combined:
if seconds, ok := timeZone[tz]; ok {
    return seconds
}

Deleting Entries

delete(timeZone, "PDT")  // Safe even if key doesn't exist

Implementing a Set

Use map[T]bool:

attended := map[string]bool{"Ann": true, "Joe": true}

if attended[person] {  // false if not in map
    fmt.Println(person, "was at the meeting")
}

Printing

The fmt package provides rich formatted printing.

Basic Functions

Function Output
Printf Formatted to stdout
Sprintf Returns formatted string
Fprintf Formatted to io.Writer
Print/Println Default format
fmt.Printf("Hello %d\n", 23)
fmt.Println("Hello", 23)
s := fmt.Sprintf("Hello %d", 23)

The %v Format

%v prints any value with a reasonable default:

fmt.Printf("%v\n", timeZone)
// map[CST:-21600 EST:-18000 MST:-25200 PST:-28800 UTC:0]

For structs:

  • %v: values only
  • %+v: with field names
  • %#v: full Go syntax
type T struct {
    a int
    b float64
    c string
}
t := &T{7, -2.35, "abc\tdef"}

fmt.Printf("%v\n", t)   // &{7 -2.35 abc   def}
fmt.Printf("%+v\n", t)  // &{a:7 b:-2.35 c:abc     def}
fmt.Printf("%#v\n", t)  // &main.T{a:7, b:-2.35, c:"abc\tdef"}

Other Useful Formats

Format Purpose
%T Type of value
%q Quoted string
%x Hex (strings, bytes, ints)

The Stringer Interface

Define String() string to control default formatting:

func (t *T) String() string {
    return fmt.Sprintf("%d/%g/%q", t.a, t.b, t.c)
}

Warning: Don’t call Sprintf with %s on the receiver—infinite recursion:

// Bad: infinite recursion
func (m MyString) String() string {
    return fmt.Sprintf("MyString=%s", m)
}

// Good: convert to basic type
func (m MyString) String() string {
    return fmt.Sprintf("MyString=%s", string(m))
}

Constants and iota

Constants are created at compile time and can only be numbers, characters, strings, or booleans.

iota Enumerator

iota creates enumerated constants:

type ByteSize float64

const (
    _           = iota // ignore first value (0)
    KB ByteSize = 1 << (10 * iota)
    MB
    GB
    TB
    PB
    EB
)

Combine with String() for automatic formatting:

func (b ByteSize) String() string {
    switch {
    case b >= EB:
        return fmt.Sprintf("%.2fEB", b/EB)
    case b >= PB:
        return fmt.Sprintf("%.2fPB", b/PB)
    // ... etc
    }
    return fmt.Sprintf("%.2fB", b)
}

Copying

Advisory: This is a best practice recommendation from Go Wiki CodeReviewComments.

To avoid unexpected aliasing, be careful when copying a struct from another package. For example, bytes.Buffer contains a []byte slice. If you copy a Buffer, the slice in the copy may alias the array in the original, causing subsequent method calls to have surprising effects.

// Dangerous: copying a bytes.Buffer
var buf1 bytes.Buffer
buf1.WriteString("hello")

buf2 := buf1  // buf2's internal slice may alias buf1's array!
buf2.WriteString(" world")  // May affect buf1 unexpectedly

General rule: Do not copy a value of type T if its methods are associated with the pointer type *T.

This applies to many types in the standard library and third-party packages:

  • bytes.Buffer
  • sync.Mutex, sync.WaitGroup, sync.Cond
  • Types containing the above
// Bad: copying a mutex
var mu sync.Mutex
mu2 := mu  // Copying a mutex is almost always a bug

// Good: use pointers or embed carefully
type SafeCounter struct {
    mu    sync.Mutex
    count int
}

// Pass by pointer, not by value
func increment(sc *SafeCounter) {
    sc.mu.Lock()
    sc.count++
    sc.mu.Unlock()
}

Quick Reference

Topic Key Point
new(T) Returns *T, zeroed
make(T) Slices, maps, channels only; returns T, initialized
Arrays Values, not references; size is part of type
Slices Reference underlying array; use append
Maps Key must support ==; use comma-ok for presence
Copying Don’t copy T if methods are on *T; beware aliasing
%v Default format for any value
%+v Struct with field names
%#v Full Go syntax
iota Enumerated constants

See Also

  • go-style-core – Core Go style principles
  • go-control-flow – Control structures including range
  • go-interfaces – Interface patterns and embedding
  • go-concurrency – Channels and goroutines