gocopy generates CopyFrom(src) and Copy() methods that make a struct
value an independent copy of another — without allocating what the
destination already owns.
*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.
| 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.
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.
go get -tool github.com/darxnet/gocopy/cmd/gocopyRequires Go 1.24 (go get -tool). No dependencies: the package is loaded with
go list -export and go/importer from the standard library.
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.
srcanddstmust not be the same value or share slices:dst's arrays are overwritten.- A nested type's
CopyFromis 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 aCopyFrom.
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.Mapsemantics and no interface boxing, for read-mostly data - goreset — generates
Reset()for pooled structs: back to the zero state, memory kept
Apache License 2.0. See LICENSE for details.