Clean code in Go · 3 MIN READ

Ownership: copy mutable data deliberately

Make it clear whether a caller lends, shares or transfers a buffer.

Make it clear whether a caller lends, shares or transfers a buffer. A slice is a view of a backing array, so assignment does not copy its elements. Trace the worked Go example, then compare the benefit with the cost before applying this design to your own project.

Clean Go · A MAP OF THE IDEA

  1. Caller supplies mutable bytes
  2. Constructor copies retained state
  3. Accessor returns a separate copy

Useful when

  • An object retains caller-owned mutable input
  • Unexpected aliasing causes hard-to-find changes

Watch out

  • Deep structures need more than a shallow copy
  • Copies should be measured on hot paths

Implementation & operation cost: Low to medium

01

The design decision

A slice is a view of a backing array, so assignment does not copy its elements. An API that retains caller-owned bytes must define whether later mutation is allowed. Copying at an ownership boundary can be more valuable than making every method look immutable.

02

A complete Go example

Read this program from the entry point and follow the calls shown in the diagram. The output is included at the end so you can check the behavior without a compiler.

go
package main

import (
	"bytes"
	"fmt"
)

type Message struct{ body []byte }

func NewMessage(input []byte) Message { return Message{body: bytes.Clone(input)} }
func (m Message) Bytes() []byte       { return bytes.Clone(m.body) }

func main() {
	input := []byte("go")
	m := NewMessage(input)
	input[0] = 'n'
	output := m.Bytes()
	output[0] = 'x'
	fmt.Println(string(m.Bytes()))
}

// Output:
// go
Read the code

The constructor copies input and Bytes copies output, so neither caller mutation changes the retained message. This makes the ownership contract concrete. It does not make arbitrary maps, nested pointers or concurrent methods safe automatically.

03

Tradeoffs and the simpler alternative

Copies cost memory and time, so measure large buffers before copying everywhere. A documented transfer or read-only borrowing contract may be preferable in a hot path. Mutexes protect shared access only when every relevant access follows the same lock discipline.

04

Describe ownership in the API contract

Returning a slice copies its descriptor, not its backing array. A caller can mutate shared elements even when append later allocates new storage. State whether the result is borrowed, immutable by convention or owned by the caller. Clone at the boundary when independent mutation is expected. A shallow clone of a slice of pointers still shares pointed-to values, so deep-copy behavior must match the actual domain structure.

05

Choose one concurrency model for mutable state

Either one goroutine owns the state and receives commands, a lock protects every relevant access, or callers receive isolated immutable snapshots. Mixing these models without a clear rule leaves races. A mutex guarding a map does not protect a slice stored in it after the method returns that slice. Return a snapshot under the lock, or keep later access inside the owner boundary. Test the aliasing case as well as running the race detector.

06

Make cleanup ownership visible

The code that successfully acquires a resource should define who closes it. Defer cleanup immediately after acquisition unless a documented ownership transfer occurs. A producer closes the channel it owns; arbitrary receivers should not close it to signal cancellation. Pass context for cancellation rather than sharing a mutable stop boolean. For a library API, document whether Close waits for goroutines and whether concurrent calls are supported.

TAKE THIS WITH YOU

Defines who may retain and mutate shared data. Copies cost memory and time, so measure large buffers before copying everywhere. A documented transfer or read-only borrowing contract may be preferable in a hot path. Mutexes protect shared access only when every relevant access follows the same lock discipline.

Check your understanding

Which statement best describes Ownership: copy mutable data deliberately?

Go to the source

Progress stays in this browser. No account needed.