gocopy

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 14, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

README

gocopy

Go Reference Go Coverage

gocopy generates CopyFrom(src) and Copy() methods that make a struct value an independent copy of another — without allocating what the destination already owns.

Why gocopy?

*dst = *src is a shallow copy: every slice and map in dst now shares memory with src, and the first write to one shows up in the other. The deep copy by hand allocates a fresh slice for every field on every call, and goes stale the day a field is added. gocopy writes the method from the struct definition and reuses dst's memory:

// Code generated by gocopy; DO NOT EDIT.

// CopyFrom makes dst an independent copy of src: one bulk copy, then the
// fields that would share memory get dst's own, reusing its capacity.
func (dst *Request) CopyFrom(src *Request) {
	keepData := dst.Data[:0]
	keepInner := dst.Inner

	*dst = *src
	dst.Data = append(keepData, src.Data...)
	dst.Inner = keepInner
	dst.Inner.CopyFrom(&src.Inner)
}

// Copy returns a new independent copy of src.
func (src *Request) Copy() (dst Request) {
	dst.CopyFrom(src)
	return dst
}

A CopyFrom into a pooled, already-sized dst allocates nothing.

What it does per field

Field Copy
slice append(dst.F[:0], src.F...) — into dst's own array
map entry by entry into dst's own map (clear first; a nil dst map is made, a nil src leaves dst empty)
struct with CopyFrom(*T) dst.F.CopyFrom(&src.F)
pointer to struct with CopyFrom(*T) into dst's own object; a nil dst gets a new one, a nil src gives nil
array of slices / of structs with CopyFrom element by element
everything else (numbers, strings, pointers, interfaces, chans, funcs, plain structs) assigned as it is

Struct tags change the treatment of a field:

Tag Effect
copy:"-" dst keeps its own value
copy:"shallow" assigned as it is, even if it is a slice or a map — on purpose, and visibly

A field holding a sync or sync/atomic type (also inside a nested struct or array, by value) must carry one of the two tags; an array of maps too.

Two modes

Bulk (the default): dst's own slices, maps and nested values are saved, the struct is assigned in one go, the saved fields are put back and filled from src. The type also gets Copy().

Field by field: a struct that holds a lock or an atomic cannot be assigned as a whole (go vet copylocks), so every field is copied on its own, and there is no Copy(): such a value is not meant to be returned.

The mode is chosen per type, and shown in the comment of the generated method.

Installation

go get -tool github.com/darxnet/gocopy/cmd/gocopy

Requires Go 1.24 (go get -tool). No dependencies: the package is loaded with go list -export and go/importer from the standard library.

Usage

Put the directive next to the type and run go generate:

//go:generate go tool gocopy -type=Request,Response
type Request struct {
	Data  []byte
	Inner Inner
	Cache map[string]int `copy:"-"`
	Mu    sync.Mutex     `copy:"-"`
}

This writes request_copy.gen.go and response_copy.gen.go in the same package. -dir points the generator at another package.

The generator is also a library — gocopy.Load and Package.Generate — for tools that want the source without the file.

Rules of the generated CopyFrom

  • src and dst must not be the same value or share slices: dst's arrays are overwritten.
  • A nested type's CopyFrom is found by its method set when the generator runs: generate (or write) the inner type's first.
  • Pointers other than "pointer to a struct with CopyFrom" are shared with src. That is usually right for references to long-lived objects; for an owned object, give its type a CopyFrom.

See also

The sibling modules follow the same rule — nothing allocated on the hot path, the zero value ready to use:

  • goshard — sharded, lock-based concurrent map for high-churn data: stores allocate nothing
  • gosync — generic concurrent map with sync.Map semantics and no interface boxing, for read-mostly data
  • goreset — generates Reset() for pooled structs: back to the zero state, memory kept

License

Apache License 2.0. See LICENSE for details.

Documentation

Overview

Package gocopy generates CopyFrom and Copy methods that make a struct value an independent copy of another without allocating what the destination already owns.

A generated CopyFrom(src) is one bulk assignment (*dst = *src) followed by fixes for the fields that would otherwise share memory: slices are copied into dst's own backing arrays (reusing their capacity), maps entry by entry into dst's own map, nested values with a CopyFrom through it. Pointers, interfaces, channels and funcs are assigned as they are. Struct tags choose a different treatment per field:

Buf   []byte  `copy:"-"`        // dst keeps its own value
Table []byte  `copy:"shallow"`  // assigned as it is, on purpose

A struct holding a lock or an atomic (also nested, by value) cannot be assigned as a whole: such fields must be tagged, the type is copied field by field, and it gets no Copy method.

The usual way to run it is a go:generate directive next to the type:

//go:generate go tool gocopy -type=Request

which writes request_copy.gen.go in the same package. Load and Package.Generate expose the generator to other tools.

Index

Constants

This section is empty.

Variables

View Source
var ErrNoPackage = errors.New("gocopy: no package found")

ErrNoPackage is returned by Load when dir holds no Go package.

Functions

func FileName

func FileName(typeName string) string

FileName is the file Generate's output is meant for: <type>_copy.gen.go, lower case.

Types

type Package

type Package struct {
	// contains filtered or unexported fields
}

Package is a loaded Go package whose struct types Generate can describe.

func Load

func Load(dir string) (*Package, error)

Load type-checks the package in dir (the current directory when empty).

The dependencies come as export data from `go list -export`; the package itself is checked from source and its own type errors are tolerated, so a stale generated file (one that names a field that no longer exists) does not stop the type from being generated again. A dependency that does not compile is an error.

func (*Package) Generate

func (p *Package) Generate(typeName string) ([]byte, error)

Generate returns the gofmt-ed source of a file declaring `func (dst *T) CopyFrom(src *T)` — and `func (src *T) Copy() T` when T holds no lock — for the struct type named typeName.

func (*Package) Name

func (p *Package) Name() string

Name is the package name, as the generated file declares it.

Directories

Path Synopsis
cmd
gocopy command
Command gocopy writes a CopyFrom and Copy methods for the struct types named by -type into <type>_copy.gen.go in the current package.
Command gocopy writes a CopyFrom and Copy methods for the struct types named by -type into <type>_copy.gen.go in the current package.
internal
cases/bulk
Package bulk is a gocopy test case: a struct without locks, copied in one assignment with the sharing fields fixed after it.
Package bulk is a gocopy test case: a struct without locks, copied in one assignment with the sharing fields fixed after it.
cases/consts
Package consts holds a type a copied map refers to, so the generated code needs an import for it.
Package consts holds a type a copied map refers to, so the generated code needs an import for it.
cases/fieldwise
Package fieldwise is a gocopy test case: a struct that cannot be assigned as a whole (it holds a lock, an atomic and a struct with a lock), so every field is copied on its own and there is no Copy method.
Package fieldwise is a gocopy test case: a struct that cannot be assigned as a whole (it holds a lock, an atomic and a struct with a lock), so every field is copied on its own and there is no Copy method.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL