deepclone

package module
v0.2.17 Latest Latest
Warning

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

Go to latest
Published: Jul 3, 2026 License: MIT Imports: 7 Imported by: 3

README

DeepClone

A high-performance deep cloning library for Go values whose state can be represented as memory-owned data.

Go Module License Go Report Card

DeepClone is intentionally honest: it clones supported in-memory values, preserves supported object relationships, and returns an error for runtime or resource state that cannot be meaningfully deep-cloned.

For the durable clone contract and forbidden semantics, see SPECS/01-clone-contract.md.

Features

  • Generic API: Clone Go values with deepclone.Clone(value).
  • Honest errors: Unsupported state returns UnsupportedError with path, type, and reason.
  • Fast common paths: Primitives, scalar slices, and scalar maps avoid reflection overhead.
  • Graph safety: Pointer cycles, map cycles, slice cycles, and shared pointer targets are preserved.
  • Custom cloning: Implement Cloner[T] for private invariants or domain-specific behavior.
  • Concurrent use: Package state is safe under concurrent cloning.
  • No runtime dependencies: Core library uses only the Go standard library.

Installation

go get github.com/kaptinlin/deepclone

Requires Go 1.26.4 or later.

Quick Start

package main

import (
	"fmt"

	"github.com/kaptinlin/deepclone"
)

func main() {
	original := map[string][]int{
		"numbers": {1, 2, 3},
		"scores":  {85, 90, 95},
	}

	cloned, err := deepclone.Clone(original)
	if err != nil {
		panic(err)
	}

	original["numbers"][0] = 999

	fmt.Println(original["numbers"])
	fmt.Println(cloned["numbers"])
}

Output:

[999 2 3]
[1 2 3]

API

func Clone[T any](src T) (T, error)
func MustClone[T any](src T) T

type Cloner[T any] interface {
	Clone() (T, error)
}

type UnsupportedError struct {
	Path   string
	Type   reflect.Type
	Reason string
}

Use Clone in production paths where unsupported state should be handled. Use MustClone for tests, fixtures, and values that are already known to be supported.

Usage

Clone structs and collections
type User struct {
	Name    string
	Friends []string
	Config  map[string]any
}

user := User{
	Name:    "Alice",
	Friends: []string{"Bob", "Charlie"},
	Config:  map[string]any{"theme": "dark"},
}

cloned, err := deepclone.Clone(user)
if err != nil {
	return err
}

cloned.Friends[0] = "Eve"

user.Friends remains unchanged because supported slices, maps, arrays, structs, pointers, and interfaces are cloned recursively.

Handle unsupported state
type Worker struct {
	Ch chan int
}

_, err := deepclone.Clone(map[string]Worker{
	"main": {Ch: make(chan int)},
})
if err != nil {
	var unsupported *deepclone.UnsupportedError
	if errors.As(err, &unsupported) {
		fmt.Println(unsupported.Path)   // $["main"].Ch
		fmt.Println(unsupported.Reason) // channels cannot be cloned
	}
}

Unsupported errors are stable and intentionally do not include value contents.

Customize clone behavior
type Document struct {
	Title   string
	Content []byte
	Version int
}

func (d Document) Clone() (Document, error) {
	content, err := deepclone.Clone(d.Content)
	if err != nil {
		return Document{}, err
	}
	return Document{
		Title:   d.Title,
		Content: content,
		Version: d.Version + 1,
	}, nil
}

Types that implement Cloner[T] control their own cloning behavior. Circular reference detection does not apply inside custom Clone methods.

Semantics

DeepClone preserves supported object relationships:

  • pointer cycles
  • map cycles
  • slice cycles
  • shared pointer targets
  • shared non-empty slice values with the same length and capacity
  • pointer-to-struct-field relationships when the addressable owner is cloned in the same graph
  • pointer-to-array-element relationships when the addressable owner is cloned in the same graph

Slice nilness and length are preserved. Slice capacity is an allocation detail and is not part of the public contract.

DeepClone does not promise full backing-array alias reconstruction for distinct subslices, and it does not promise map entry interior pointer reconstruction.

Special Cases

Value kind Clone behavior
Nil pointers, slices, maps, interfaces, channels, functions, unsafe pointers Preserved as nil
Non-nil channels Return UnsupportedError
Non-nil functions Return UnsupportedError
Non-nil unsafe pointers Return UnsupportedError
Sync primitives and atomic state Return UnsupportedError
File handles Return UnsupportedError
Unexported value-like struct fields Preserved by shallow struct copy
Unexported direct or nested reference-like struct fields Return UnsupportedError; implement Cloner[T] for private invariants

Performance

DeepClone keeps common operations fast with primitive, scalar slice, and scalar map fast paths plus cached reflection metadata for structs.

task bench

Run comparison benchmarks against other clone libraries:

task bench-comparison

When publishing benchmark numbers, record the date, CPU, OS, Go version, command, and raw output source. Comparison numbers are meaningful only after the specific clone semantics being compared are checked.

Development

task deps   # Download and tidy dependencies
task lint   # Run golangci-lint and tidy checks
task test   # Run tests with -race
task verify # Run the full local verification pipeline

For development rules, see AGENTS.md.

Examples

See examples/ for runnable examples covering basic values, circular references, and custom clone behavior.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Overview

Package deepclone copies Go values whose state can be represented as memory-owned data.

Clone returns a deep copy or an error when a value cannot be honestly cloned. MustClone is the convenience form for values that are known to be supported.

Reflection cloning preserves supported object graphs, including circular references. Nil pointers, slices, maps, interfaces, channels, functions, and unsafe pointers keep their nil meaning. Non-nil channels, functions, and unsafe pointers are rejected because they represent runtime identity or execution capability rather than ordinary memory-owned data. Slice capacity is an allocation detail, not a public cloning guarantee. Pointer-to-field and pointer-to-array-element relationships are preserved only when the addressable owner is cloned in the same graph.

The package does not use unsafe to read or write unexported fields. Reflection cloning preserves value-like unexported fields by shallow-copying the struct first, but rejects unexported reference-like state that it cannot safely deep-clone. Types with private invariants or resource ownership should implement Cloner[T] and define their own behavior.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Clone

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

Clone returns a deep copy of src.

Clone preserves circular references when it uses reflection. Types that implement Cloner[T] control their own cloning behavior.

Example
package main

import (
	"fmt"

	"github.com/kaptinlin/deepclone"
)

func main() {
	original := map[string][]int{
		"scores": {90, 85, 77},
	}
	cloned, err := deepclone.Clone(original)
	if err != nil {
		panic(err)
	}

	// Modify the clone — original is unaffected
	cloned["scores"][0] = 100

	fmt.Println("original:", original["scores"])
	fmt.Println("cloned:  ", cloned["scores"])
}
Output:
original: [90 85 77]
cloned:   [100 85 77]
Example (Nil)
package main

import (
	"fmt"

	"github.com/kaptinlin/deepclone"
)

func main() {
	var original []int
	cloned := deepclone.MustClone(original)

	fmt.Println("nil preserved:", cloned == nil)
}
Output:
nil preserved: true
Example (Slice)
package main

import (
	"fmt"

	"github.com/kaptinlin/deepclone"
)

func main() {
	original := []string{"a", "b", "c"}
	cloned := deepclone.MustClone(original)

	cloned[0] = "z"

	fmt.Println("original:", original)
	fmt.Println("cloned:  ", cloned)
}
Output:
original: [a b c]
cloned:   [z b c]
Example (Struct)
package main

import (
	"fmt"

	"github.com/kaptinlin/deepclone"
)

func main() {
	type Address struct {
		City  string
		State string
	}
	type Person struct {
		Name    string
		Age     int
		Address *Address
	}

	original := Person{
		Name: "Alice",
		Age:  30,
		Address: &Address{
			City:  "Portland",
			State: "OR",
		},
	}
	cloned := deepclone.MustClone(original)

	// Modify the clone's nested pointer — original is unaffected
	cloned.Address.City = "Seattle"
	cloned.Address.State = "WA"

	fmt.Println("original:", original.Address.City, original.Address.State)
	fmt.Println("cloned:  ", cloned.Address.City, cloned.Address.State)
}
Output:
original: Portland OR
cloned:   Seattle WA

func MustClone added in v0.2.17

func MustClone[T any](src T) T

MustClone returns a deep copy of src or panics if src cannot be cloned.

Types

type Cloner added in v0.2.17

type Cloner[T any] interface {
	// Clone returns a copy that can be used independently of the original.
	Clone() (T, error)
}

Cloner lets a type define its own deep-cloning behavior.

Clone must return a copy that can be used independently of the original. Circular reference detection does not apply inside custom Clone methods.

Example
package main

import (
	"fmt"

	"github.com/kaptinlin/deepclone"
)

// Document is a type that implements the Cloner interface
// to provide custom deep cloning behavior.
type Document struct {
	Title string
	Tags  []string
}

func (d Document) Clone() (Document, error) {
	tags, err := deepclone.Clone(d.Tags)
	if err != nil {
		return Document{}, err
	}
	return Document{
		Title: d.Title,
		Tags:  tags,
	}, nil
}

func main() {
	original := Document{
		Title: "Guide",
		Tags:  []string{"go", "clone"},
	}
	cloned := deepclone.MustClone(original)

	cloned.Tags[0] = "rust"

	fmt.Println("original:", original.Tags)
	fmt.Println("cloned:  ", cloned.Tags)
}
Output:
original: [go clone]
cloned:   [rust clone]

type UnsupportedError added in v0.2.17

type UnsupportedError struct {
	Path   string
	Type   reflect.Type
	Reason string
}

UnsupportedError reports a value that cannot be honestly deep-cloned.

func (*UnsupportedError) Error added in v0.2.17

func (e *UnsupportedError) Error() string

Directories

Path Synopsis
examples
basic command
Package main demonstrates basic usage of the deepclone library.
Package main demonstrates basic usage of the deepclone library.
circular command
Package main demonstrates handling circular references with the deepclone library.
Package main demonstrates handling circular references with the deepclone library.
custom command
Package main demonstrates custom cloning behavior with the deepclone library.
Package main demonstrates custom cloning behavior with the deepclone library.

Jump to

Keyboard shortcuts

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