Package reflect provides a generic DeepCopy for Go, the external
implementation of proposal go.dev/issue/51520.
import "golang.design/x/reflect"src := &Node{Val: 1, Next: &Node{Val: 2}}
dst := reflect.DeepCopy(src) // a fully independent copyDeepCopy copies src to a freshly allocated value of the same type and
returns it:
func DeepCopy[T any](src T, opts ...DeepCopyOption) (dst T)Two values of identical type are deeply copied as follows:
- Numbers, bools and strings are copied and have a different underlying memory address.
- Slices and arrays are deeply copied, including their elements.
- Maps are deeply copied for all keys and values.
- Pointers are deeply copied for the pointed value, and the copy points to the deeply copied value.
- Structs are deeply copied for all fields, including unexported ones.
- Interfaces are deeply copied if the underlying type can be deeply copied.
A few cases mean a deeply copied value is not necessarily DeepEqual to the
source:
- Func values still refer to the same function.
- Bidirectional channels are replaced by newly created channels.
- One-way (send- or receive-only) channels still refer to the same channel.
Stateful objects (sync.Mutex, os.File, net.Conn, js.Value, ...) and
singletons should usually not be copied by their memory representation. Use the
following options to override the default behavior per type:
dst := reflect.DeepCopy(src,
// Keep a singleton shared by reference instead of copying it.
reflect.RetainType[*Config](),
// Reset a stateful value to its zero value (e.g. an unlocked mutex).
reflect.ZeroType[sync.Mutex](),
// General escape hatch: provide arbitrary per-type copy logic. T may be a
// concrete type or an interface (applies to any implementing dynamic type).
reflect.WithCopyFunc(func(c net.Conn) net.Conn { return reconnect(c) }),
)The type parameter may be a concrete type or an interface; an interface applies
to every value whose dynamic type implements it. RetainType and ZeroType are
thin wrappers over WithCopyFunc.
Additional options control structural behavior:
DisallowCopyUnexported()skips unexported struct fields instead of copying them.DisallowCopyCircular()panics on circular structures instead of handling them.DisallowCopyBidirectionalChan()keeps the source channel instead of creating a new one.DisallowType[T]()panics if a value of type T (concrete or interface) is encountered.
These answer the questions raised in go.dev/issue/51520:
- Circular and shared structures are handled by tracking already-copied
pointers, so the copy preserves the original pointer aliasing. Opt out with
DisallowCopyCircular(). - The destination can never alias the source.
DeepCopyalways allocates a fresh result, so the "destination reachable from source" hazard does not arise. - Unexported fields are copied (this requires the
unsafepackage, sincereflectcannot set unexported fields directly). Opt out withDisallowCopyUnexported(). - Singletons and stateful objects are the cases where blindly copying memory
is wrong;
RetainType,ZeroTypeandWithCopyFuncexist to handle them without baking aClone-style interface into the type system. - Cost. Deep copy is not free: it allocates roughly one allocation per
copied element. See
bench_test.go. Prefer a hand-written copy on hot paths.
MIT | © 2022 The golang.design Initiative Authors, written by Changkun Ou.