Skip to content

proposal: reflect: add DeepCopy  #51520

@changkun

Description

@changkun
Previous proposal updates As a duality complement of `DeepEqual`, I propose to add a new API for deep copy:
package reflect

// DeepCopy copies src to dst recursively.
//
// Two values of identical type are deeply copied if one of the following cases applies.
//
// Array and slices values are deeply copied, including its elements.
//
// Struct values are deeply copied for all fields, including exported and unexported.
//
// Interface values are deeply copied if the underlying type can be deeply copied.
//
// Map values are deeply copied for all of its key and corresponding values.
//
// Pointer values are deeply copied for its pointed value.
//
// One exception is Func value. It is not copiable, and still points to the same function.
//
// Other values - numbers, bools, strings, and channels - are deeply copied and
// have different underlying memory address.
func DeepCopy[T any](dst, src T)
// or
//
// DeepCopy panics if src and dst have different types.
func DeepCopy(dst, src any)
// or
//
// DeepCopy returns an error if src and dst have different types.
func DeepCopy(dst, src any) error

DeepCopy may likely be very commonly used. Implement it in reflect package may receive better runtime performance.

The frist version seems more preferrable because type parameters permits compile time type checking, but the others might also be optimal.

The proposed document of the API is preliminary.


Update:

Based on the discussions until #51520 (comment), the documented behavior could be:

// DeepCopy copies src to dst recursively.
//
// Two values of identical type are deeply copied if one of the following
// cases apply.
//
// Array and slices values are deeply copied, including its elements.
//
// Struct values are deeply copied for all fields, including exported
// and unexported.
//
// Map values are deeply copied for all of its key and corresponding
// values.
//
// Pointer values are deeply copied for their pointed value, and the
// pointer points to the deeply copied value.
//
// Numbers, bools, strings are deeply copied and have different underlying
// memory address.
//
// Interface values are deeply copied if the underlying type can be
// deeply copied.
//
// There are a few exceptions that may result in a deeply copied value not
// deeply equal (asserted by DeepEqual(dst, src)) to the source value:
//
// 1) Func values are still refer to the same function
// 2) Chan values are replaced by newly created channels
// 3) One-way Chan values (receive or read-only) values are still refer
//    to the same channel
//
// Note that for stateful copied values, such as the lock status in
// sync.Mutex, or underlying file descriptors in os.File and net.Conn,
// are retained but may result in unexpected consequences in follow-up
// usage, the caller should clear these values depending on usage context.
//
// The function panics/returns with an error if
//
// 1) source and destination values have different types
// 2) destination value is reachable from source value

Depending on the design choice, the content included and after exceptions could decide on different behaviors, see
#51520 (comment), and #51520 (comment) as examples.

The API signature could select one of the following possible options. The main differences between them include two aspects:

  1. panic (break the code flow, and enter defer recover) or return an error (user handled directly)
  2. destination value in either parameter (more GC friendly) or return value (always allocates)
func DeepCopy[T any](dst, src T) error
func DeepCopy[T any](dst, src T)
func DeepCopy(dst, src any) error
func DeepCopy(dst, src any)

func DeepCopy[T any](src T) (T, error)
func DeepCopy[T any](src T) T
func DeepCopy(src any) (any, error)
func DeepCopy(src any) error

Either version is optimal, and purely depending on the final choice.

Update (until #51520 (comment)):

I propose to add a new API for deep copy:

// DeepCopy copies src to dst recursively.
//
// Two values of identical type are deeply copied if one of the following
// cases apply.
//
// Numbers, bools, strings are deeply copied and have different underlying
// memory address.
//
// Slice and Array values are deeply copied, including its elements.
//
// Map values are deeply copied for all of its key and corresponding
// values.
//
// Pointer values are deeply copied for their pointed value, and the
// pointer points to the deeply copied value.
//
// Struct values are deeply copied for all fields, including exported
// and unexported.
//
// Interface values are deeply copied if the underlying type can be
// deeply copied.
//
// There are a few exceptions that may result in a deeply copied value not
// deeply equal (asserted by DeepEqual(dst, src)) to the source value:
//
// 1) Func values are still refer to the same function
// 2) Chan values are replaced by newly created channels
// 3) One-way Chan values (receive or read-only) values are still refer
//    to the same channel
//
// Note that while correct uses of DeepCopy do exist, they are not rare.
// The use of DeepCopy often indicates the copying object does not contain
// a singleton or is never meant to be copied, such as sync.Mutex, os.File,
// net.Conn, js.Value, etc. In these cases, the copied value retains the
// memory representations of the source value but may result in unexpected
// consequences in follow-up usage, the caller should clear these values
// depending on their usage context.
func DeepCopy[T any](src T) (dst T)

Here is an implementation for trying out: https://pkg.go.dev/golang.design/x/reflect

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions