Skip to content

seyallius/doppel

Repository files navigation

doppel

Your data's doppelgänger — deep copies without side effects.

Go Reference Go Version License

doppel is a Go library for safe, explicit deep cloning of complex data structures. It provides a minimal, zero-reflection API built around composable generic helpers that integrate directly into your type's Clone() method.

Go assignment is a shallow copy. Structs with pointer fields, slices, and maps silently share memory between "originals" and "copies," leading to subtle bugs. doppel solves this by giving you full control over every field, every allocation, and every edge case.


Why doppel?

Principle What it means
Explicit Every clone path is visible and auditable — no hidden behavior.
Composable CloneSlice, CloneMap, ClonePointer wire together in your Clone().
Zero reflection No magic, maximum speed. Generics-based helpers with no runtime overhead.
Code generation Optional doppelgen tool generates Clone() methods from struct tags.

Architecture

core          — Interfaces: Cloner[T], SelfClonable[T], FuncCloner[T], error types, tag contract
manual        — Generic helpers: CloneSlice, CloneMap, ClonePointer, Identity
doppel        — Top-level API: Clone[T], MustClone[T]

Clone dispatch

doppel.Clone(user)  →  user.Clone()  →  manual.CloneSlice + CloneMap + ClonePointer
                        (zero overhead)      (fully visible, fully controlled)

There is no priority chain, no registry, and no reflection. You own the clone path.


Quick start

1. Define your type

type User struct {
    ID     int64
    Name   string
    Active bool
    Tags   []string
    Scores map[string]int
}

2. Implement Clone()

func (u *User) Clone() (*User, error) {
    if u == nil { return nil, nil }

    tags, err := manual.CloneSlice(u.Tags, manual.Identity[string])
    if err != nil {
        return nil, core.WrapError("User.Tags", err)
    }

    scores, err := manual.CloneMap(u.Scores, manual.Identity[int])
    if err != nil {
        return nil, core.WrapError("User.Scores", err)
    }

    return &User{ID: u.ID, Name: u.Name, Active: u.Active, Tags: tags, Scores: scores}, nil
}

3. Call doppel.Clone

cloned, err := doppel.Clone(original)

// Mutate the original — the clone is unaffected
original.Tags[0] = "mutated"
// cloned.Tags[0] still has the original value

Code Generation with doppelgen

doppelgen reads your Go source files, finds structs annotated with doppel:"..." tags, and generates *.clone_gen.go files that implement SelfClonable[T]. It also generates companion *.clone_gen_test.go test files that verify nil safety, deep-copy independence, skip/shallow/empty field semantics, and more.

Quick start

Add the following directive to any go file (typically in any main like file).

//go:generate go run github.com/seyallius/doppel/cmd/doppelgen --type=MyStruct

Then run:

go generate ./...

The generator produces files like user.clone_gen.go with a header Code generated by doppelgen. DO NOT EDIT.

A runnable example is present in main.go. It generates structs for types in testdata in preview mode (remove --preview for actual file generation).

If you want full control, install doppelgen locally (see doppelgen installation) and run the CLI yourself.

Flags

Flag Description
--type Comma-separated struct names
--package Target package directory (default: same)
--output Output directory
--tag Custom struct tag name (default: doppel)
--preview Print generated code to stdout

Installation

// Using in code
go get github.com/seyallius/doppel

// Installing doppelgen
go install github.com/seyallius/doppel/cmd/doppelgen@latest

Requires Go 1.25 or later.


Features

  • Zero-reflection cloning — generics-based helpers, no runtime overhead
  • Composable helpersCloneSlice, CloneMap, ClonePointer wire together in Clone()
  • Contextual errors — field-path annotated errors with errors.Is/errors.As support
  • Nil-safety — all helpers preserve nil/empty distinction
  • Thread-safe — all public types are safe for concurrent use
  • Struct tagsdoppel:"-", doppel:"shallow", doppel:"clone", doppel:"deep" for code generation
  • Zero dependencies — only the Go standard library

Performance

Approach ns/op B/op allocs/op
Manual Clone() (User) ~750 ~480 ~12
Manual Clone() (Order) ~1200 ~800 ~20
Shallow copy (baseline) ~1 0 0

See Benchmarks for detailed data.


Documentation

# Page Topic
1 Getting Started Install, first clone, API guide
2 SelfClonable The Clone() method pattern
3 Manual Helpers CloneSlice, CloneMap, ClonePointer
4 Struct Tags Tag directives for code generation
5 Benchmarks Performance data
6 API Reference Complete function signatures

Development

just test              # Run all tests
just test-coverage     # Generate coverage report
just bench             # Run all benchmarks
just all-checks        # Format, vet, lint, staticcheck
just generate-nav      # Update doc navigation links

License

MIT

About

Your data’s doppelgänger — deep copies without side effects.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors