├── go.mod ├── go.sum ├── unsafe_test.go ├── Makefile ├── safe.go ├── mem_test.go ├── .github └── workflows │ └── go.yml ├── unsafe.go ├── examples ├── null │ └── main.go └── simple │ └── main.go ├── copier_bench_test.go ├── mem.go ├── slice_copier.go ├── types.go ├── funcs ├── funcs.go ├── funcs_test.go └── gen.go ├── README.md ├── struct_copier.go ├── internal └── cache │ └── cache.go ├── copiers.go ├── copier.go ├── LICENSE └── copier_test.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gotidy/copy 2 | 3 | go 1.15 4 | 5 | require github.com/gotidy/ptr v1.3.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/gotidy/ptr v1.3.0 h1:5wdrH1G8X4txy6fbWWRznr7k974wMWtePWP3p6s1API= 2 | github.com/gotidy/ptr v1.3.0/go.mod h1:vpltyHhOZE+NGXUiwpVl3wV9AGEBlxhdnaimPDxRLxg= 3 | -------------------------------------------------------------------------------- /unsafe_test.go: -------------------------------------------------------------------------------- 1 | package copy 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | "unsafe" 7 | ) 8 | 9 | func TestPtrOf(t *testing.T) { 10 | var i int 11 | p := &i 12 | if PtrOf(p) != unsafe.Pointer(reflect.ValueOf(p).Pointer()) { 13 | t.Errorf("PtrOf() = %v, want %v", PtrOf(p), unsafe.Pointer(reflect.ValueOf(p).Pointer())) 14 | } 15 | if PtrOf(nil) != nil { 16 | t.Errorf("PtrOf() = %v, want %v", PtrOf(p), nil) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION := "$(shell git describe --abbrev=0)" 2 | MODULE := "$(shell git config --get remote.origin.url | sed 's|^https\://\([^ <]*\)\(.*\)\.git|\1|g')" 3 | MKFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) 4 | CURRENT_DIR := $(dir $(MKFILE_PATH)) 5 | update-pkg-cache: 6 | cd $$HOME && \ 7 | GOPROXY=https://proxy.golang.org GO111MODULE=on go get $(MODULE)@$(VERSION) 8 | 9 | gen: 10 | go generate ./... 11 | 12 | test: 13 | go test ./... 14 | go test ./... -tags=safe -------------------------------------------------------------------------------- /safe.go: -------------------------------------------------------------------------------- 1 | // +build safe 2 | 3 | package copy 4 | 5 | import ( 6 | "reflect" 7 | "unsafe" 8 | ) 9 | 10 | type Type = reflect.Type 11 | 12 | func DataOf(i interface{}) (typ Type, data unsafe.Pointer) { 13 | if i == nil { 14 | return nil, nil 15 | } 16 | v := reflect.ValueOf(i) 17 | if v.Kind() != reflect.Ptr { 18 | return nil, nil 19 | } 20 | return reflect.TypeOf(i), unsafe.Pointer(v.Pointer()) 21 | } 22 | 23 | func TypeOf(i interface{}) (typ Type) { 24 | if i == nil { 25 | return nil 26 | } 27 | return reflect.TypeOf(i) 28 | } 29 | 30 | type iface struct { 31 | Type, Data unsafe.Pointer 32 | } 33 | 34 | func PtrOf(i interface{}) unsafe.Pointer { 35 | v := reflect.ValueOf(i) 36 | if v.Kind() != reflect.Ptr { 37 | return nil 38 | } 39 | return unsafe.Pointer(v.Pointer()) 40 | } 41 | -------------------------------------------------------------------------------- /mem_test.go: -------------------------------------------------------------------------------- 1 | package copy 2 | 3 | import ( 4 | "testing" 5 | "unsafe" 6 | ) 7 | 8 | func Test_sliceAt(t *testing.T) { 9 | ii := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} 10 | s := sliceAt(unsafe.Pointer(&ii), unsafe.Sizeof(ii[0])) 11 | for i := 0; i < s.Len; i++ { 12 | if *(*int)(s.Index(i)) != ii[i] { 13 | t.Errorf("*(*int)(s.Index(i)) = %v, want %v", *(*int)(s.Index(i)), ii[i]) 14 | } 15 | } 16 | } 17 | 18 | func Test_makeSliceAt(t *testing.T) { 19 | const size = 10 20 | var ii []int 21 | s := makeSliceAt(unsafe.Pointer(&ii), unsafe.Sizeof(ii[0]), size) 22 | if len(ii) != size { 23 | t.Errorf("actual slice size is %v, expected %v", len(ii), size) 24 | } 25 | for i := 0; i < size; i++ { 26 | *(*int)(s.Index(i)) = i + 1 27 | if ii[i] != i+1 { 28 | t.Errorf("actual %v, expected %v", ii[i], i+1) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Set up Go 1.x 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: ^1.15 20 | id: go 21 | 22 | - name: Check out code into the Go module directory 23 | uses: actions/checkout@v2 24 | 25 | - name: Get dependencies 26 | run: | 27 | go get -v -t -d ./... 28 | if [ -f Gopkg.toml ]; then 29 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 30 | dep ensure 31 | fi 32 | 33 | - name: Build 34 | run: go build -v . 35 | 36 | - name: Test 37 | run: go test -v . 38 | -------------------------------------------------------------------------------- /unsafe.go: -------------------------------------------------------------------------------- 1 | // +build !safe 2 | 3 | package copy 4 | 5 | import "unsafe" 6 | 7 | // More safe and independent from internal structs 8 | // func ifaceData(i interface{}) unsafe.Pointer { 9 | // v := reflect.ValueOf(i) 10 | // if v.Kind() != reflect.Ptr { 11 | // panic("source must be pointer to struct") 12 | // } 13 | // return unsafe.Pointer(v.Pointer()) 14 | // } 15 | 16 | type Type = unsafe.Pointer 17 | 18 | func DataOf(i interface{}) (typ Type, data unsafe.Pointer) { 19 | if i == nil { 20 | return nil, nil 21 | } 22 | iface := *(*iface)(unsafe.Pointer(&i)) 23 | return iface.Type, iface.Data 24 | } 25 | 26 | func TypeOf(i interface{}) (typ Type) { 27 | if i == nil { 28 | return nil 29 | } 30 | return (*iface)(unsafe.Pointer(&i)).Type 31 | } 32 | 33 | type iface struct { 34 | Type, Data unsafe.Pointer 35 | } 36 | 37 | func PtrOf(i interface{}) unsafe.Pointer { 38 | if i == nil { 39 | return nil 40 | } 41 | // eface := *(*iface)(unsafe.Pointer(&i)) 42 | return (*iface)(unsafe.Pointer(&i)).Data 43 | } 44 | -------------------------------------------------------------------------------- /examples/null/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "github.com/gotidy/copy" 9 | "github.com/gotidy/ptr" 10 | ) 11 | 12 | func main() { 13 | src := struct { 14 | Name sql.NullString 15 | MiddleName sql.NullString 16 | Surname sql.NullString 17 | }{ 18 | Name: sql.NullString{String: "John", Valid: true}, 19 | MiddleName: sql.NullString{}, 20 | Surname: sql.NullString{String: "Kennedy", Valid: true}, 21 | } 22 | 23 | dst := struct { 24 | Name *string 25 | MiddleName *string 26 | Surname *string 27 | }{} 28 | 29 | copiers := copy.New() 30 | copier := copiers.Get(&dst, &src) 31 | copier.Copy(&dst, &src) 32 | 33 | if data, err := json.MarshalIndent(dst, "", " "); err == nil { 34 | fmt.Println(string(data)) 35 | } 36 | 37 | dst.MiddleName = ptr.String("Fitzgerald") 38 | copier = copiers.Get(&src, &dst) 39 | copier.Copy(&src, &dst) 40 | 41 | if data, err := json.MarshalIndent(dst, "", " "); err == nil { 42 | fmt.Println(string(data)) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/simple/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/gotidy/copy" 8 | ) 9 | 10 | // Person data. 11 | type Person struct { 12 | Name string 13 | MiddleName *string 14 | Surname string 15 | } 16 | 17 | // User data. 18 | type User struct { 19 | Person 20 | Email string 21 | Age int8 22 | Married bool 23 | } 24 | 25 | // Employee data. 26 | type Employee struct { 27 | Name string 28 | MiddleName string 29 | Surname string 30 | Email string 31 | Age int 32 | } 33 | 34 | func main() { 35 | src := User{ 36 | Person: Person{ 37 | Name: "John", 38 | MiddleName: nil, 39 | Surname: "Smith", 40 | }, 41 | Email: "john.smith@joy.me", 42 | Age: 33, 43 | Married: false, 44 | } 45 | dst := Employee{} 46 | 47 | copiers := copy.New() // New("json") 48 | copier := copiers.Get(&Employee{}, &User{}) 49 | copier.Copy(&dst, &src) 50 | 51 | if data, err := json.MarshalIndent(dst, "", " "); err == nil { 52 | fmt.Println(string(data)) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /copier_bench_test.go: -------------------------------------------------------------------------------- 1 | package copy 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | type internal struct { 8 | I int 9 | } 10 | 11 | type testStruct struct { 12 | S string 13 | I int 14 | B bool 15 | F float64 16 | BB []bool 17 | V internal 18 | PV *internal 19 | } 20 | 21 | var src = testStruct{ 22 | S: "string", 23 | I: 10, 24 | B: true, 25 | F: 4.9, 26 | BB: []bool{true, false}, 27 | V: internal{I: 5}, 28 | PV: &internal{I: 15}, 29 | } 30 | 31 | var dst = testStruct{} 32 | 33 | func copyStruct(dst, src *testStruct) { 34 | *dst = *src 35 | } 36 | 37 | func BenchmarkDirectCopy(b *testing.B) { 38 | for i := 0; i < b.N; i++ { 39 | copyStruct(&dst, &src) 40 | } 41 | } 42 | 43 | func BenchmarkManualCopy(b *testing.B) { 44 | for i := 0; i < b.N; i++ { 45 | dst = testStruct{ 46 | S: src.S, 47 | I: src.I, 48 | BB: src.BB, 49 | V: internal{I: src.V.I}, 50 | PV: &internal{I: src.V.I}, 51 | } 52 | } 53 | } 54 | 55 | func BenchmarkCopier(b *testing.B) { 56 | c := New() 57 | copier := c.Get(&dst, &src) 58 | 59 | for i := 0; i < b.N; i++ { 60 | copier.Copy(&dst, &src) 61 | } 62 | } 63 | 64 | func BenchmarkCopiers(b *testing.B) { 65 | c := New() 66 | c.Prepare(&dst, &src) 67 | 68 | for i := 0; i < b.N; i++ { 69 | c.Copy(&dst, &src) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /mem.go: -------------------------------------------------------------------------------- 1 | package copy 2 | 3 | import ( 4 | "reflect" 5 | "unsafe" 6 | ) 7 | 8 | func memcopy(dst, src unsafe.Pointer, size int) { 9 | var srcSlice []byte 10 | srcSH := (*reflect.SliceHeader)((unsafe.Pointer(&srcSlice))) 11 | srcSH.Data = uintptr(src) 12 | srcSH.Cap = int(size) 13 | srcSH.Len = int(size) 14 | 15 | var dstSlice []byte 16 | dstSH := (*reflect.SliceHeader)((unsafe.Pointer(&dstSlice))) 17 | dstSH.Data = uintptr(dst) 18 | dstSH.Cap = int(size) 19 | dstSH.Len = int(size) 20 | 21 | copy(dstSlice, srcSlice) 22 | } 23 | 24 | func alloc(size int) unsafe.Pointer { 25 | if size == 0 { 26 | return nil 27 | } 28 | size = (size + 7) / 8 // size in int64 29 | return unsafe.Pointer(&(make([]int64, size)[0])) 30 | } 31 | 32 | type slice struct { 33 | data unsafe.Pointer 34 | size uintptr 35 | 36 | Len int 37 | } 38 | 39 | func sliceAt(ptr unsafe.Pointer, size uintptr) slice { 40 | s := (*reflect.SliceHeader)(ptr) 41 | return slice{data: unsafe.Pointer(s.Data), size: size, Len: s.Len} 42 | } 43 | 44 | func makeSliceAt(ptr unsafe.Pointer, size uintptr, len int) slice { 45 | s := (*reflect.SliceHeader)(ptr) 46 | if s.Cap < len || s.Cap > len*2 { 47 | s.Data = uintptr(alloc(int(size) * len)) 48 | s.Cap = len 49 | } 50 | s.Len = len 51 | return slice{data: unsafe.Pointer(s.Data), size: size, Len: s.Len} 52 | } 53 | 54 | func (s slice) Index(i int) unsafe.Pointer { 55 | return unsafe.Pointer(uintptr(s.data) + s.size*uintptr(i)) 56 | } 57 | -------------------------------------------------------------------------------- /slice_copier.go: -------------------------------------------------------------------------------- 1 | package copy 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "unsafe" 7 | ) 8 | 9 | type SliceCopier struct { 10 | BaseCopier 11 | 12 | copier func(dst, src unsafe.Pointer) 13 | dstSize uintptr // Size of the destination element 14 | srcSize uintptr // Size of the source element 15 | } 16 | 17 | func NewSliceCopier(c *Copiers) *SliceCopier { 18 | copier := &SliceCopier{BaseCopier: NewBaseCopier(c)} 19 | return copier 20 | } 21 | 22 | func (c *SliceCopier) init(dst, src reflect.Type) { 23 | c.BaseCopier.init(dst, src) 24 | 25 | c.copier = c.getCopierFunc(dst.Elem(), src.Elem(), 0, 0) 26 | if c.copier == nil && !c.options.Skip { 27 | panic(fmt.Errorf(`slice element of type «%s» is not assignable to slice element of type «%s»`, src.String(), dst.String())) 28 | } 29 | c.dstSize = dst.Elem().Size() 30 | c.srcSize = src.Elem().Size() 31 | } 32 | 33 | // Copy copies the contents of src into dst. Dst and src each must be a pointer to struct. 34 | func (c *SliceCopier) Copy(dst, src interface{}) { 35 | dstType, dstPtr := DataOf(dst) 36 | srcType, srcPtr := DataOf(src) 37 | 38 | if c.src.Check(srcType) { 39 | panic("source expected type " + c.src.Name + ", but has " + reflect.TypeOf(src).String()) 40 | } 41 | if c.dst.Check(dstType) { 42 | panic("destination expected type " + c.dst.Name + ", but has " + reflect.TypeOf(dst).String()) 43 | } 44 | 45 | c.copy(dstPtr, srcPtr) 46 | } 47 | 48 | func (c *SliceCopier) copy(dst, src unsafe.Pointer) { 49 | if c.copier == nil { 50 | return 51 | } 52 | srcSlice := sliceAt(src, c.srcSize) 53 | dstSlice := makeSliceAt(dst, c.dstSize, srcSlice.Len) 54 | 55 | for i := 0; i < srcSlice.Len; i++ { 56 | c.copier(dstSlice.Index(i), srcSlice.Index(i)) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | package copy 2 | 3 | import "reflect" 4 | 5 | type ValueKind int 6 | 7 | const ( 8 | UnknownKind ValueKind = 0 9 | StructValue ValueKind = 1 10 | SliceValue ValueKind = 2 11 | MapValue ValueKind = 3 12 | PtrValue ValueKind = 0b10 13 | StructPtrValue ValueKind = StructValue + PtrValue 14 | SlicePtrValue ValueKind = SliceValue + PtrValue 15 | MapPtrValue ValueKind = MapValue + PtrValue 16 | ) 17 | 18 | func getValueKind(t reflect.Type) ValueKind { 19 | var kind ValueKind 20 | if t.Kind() == reflect.Ptr { 21 | kind = PtrValue 22 | t = t.Elem() 23 | } 24 | switch k := t.Kind(); { 25 | case k == reflect.Struct: 26 | kind += StructValue 27 | case k == reflect.Slice: 28 | kind += SliceValue 29 | // case k == reflect.Map && t.Key().Kind() == reflect.String && t.Elem().Kind() == reflect.Interface: 30 | // kind += MapValue 31 | default: 32 | return UnknownKind 33 | } 34 | return kind 35 | } 36 | 37 | func getCopier(c *Copiers, dst, src reflect.Type) internalCopier { 38 | srcKind := getValueKind(src) 39 | dstKind := getValueKind(dst) 40 | 41 | switch { 42 | case srcKind == StructValue && dstKind == StructValue: 43 | return NewStructCopier(c) 44 | case srcKind == StructValue && dstKind == StructPtrValue: 45 | return NewValueToPValueCopier(c) 46 | case srcKind == StructPtrValue && dstKind == StructValue: 47 | return NewPValueToValueCopier(c) 48 | case srcKind == StructPtrValue && dstKind == StructPtrValue: 49 | return NewPValueToPValueCopier(c) 50 | case srcKind == SliceValue && dstKind == SliceValue: 51 | return NewSliceCopier(c) 52 | case srcKind == SliceValue && dstKind == SlicePtrValue: 53 | return NewValueToPValueCopier(c) 54 | case srcKind == SlicePtrValue && dstKind == SliceValue: 55 | return NewPValueToValueCopier(c) 56 | case srcKind == SlicePtrValue && dstKind == SlicePtrValue: 57 | return NewPValueToPValueCopier(c) 58 | } 59 | return nil 60 | } 61 | -------------------------------------------------------------------------------- /funcs/funcs.go: -------------------------------------------------------------------------------- 1 | //go:generate go run gen.go 2 | //go:generate gofmt -s -w funcs.gen.go 3 | // Package funcs provides copy functions for specified types. 4 | package funcs 5 | 6 | import ( 7 | "reflect" 8 | "sync" 9 | "unsafe" 10 | ) 11 | 12 | type funcKey struct { 13 | Src reflect.Type 14 | Dst reflect.Type 15 | // Zero bool // Initiate pointer to zero value 16 | } 17 | 18 | func typeOf(v interface{}) reflect.Type { 19 | return reflect.TypeOf(v) 20 | } 21 | 22 | func typeOfPointer(v interface{}) reflect.Type { 23 | return reflect.PtrTo(reflect.TypeOf(v)) 24 | } 25 | 26 | // CopyFuncs is the storage of functions intended for copying data. 27 | type CopyFuncs struct { 28 | mu sync.RWMutex 29 | funcs map[funcKey]func(dst, src unsafe.Pointer) 30 | sizes []func(dst, src unsafe.Pointer) 31 | } 32 | 33 | // Get the copy function for the pair of types, if it is not found then nil is returned. 34 | func (t *CopyFuncs) Get(dst, src reflect.Type) func(dst, src unsafe.Pointer) { 35 | t.mu.RLock() 36 | f := t.funcs[funcKey{Src: src, Dst: dst}] 37 | t.mu.RUnlock() 38 | if f != nil { 39 | return f 40 | } 41 | 42 | if dst.Kind() != src.Kind() { 43 | return nil 44 | } 45 | 46 | if dst.Kind() == reflect.String { 47 | // TODO 48 | return nil 49 | } 50 | 51 | same := dst == src 52 | 53 | switch dst.Kind() { 54 | case reflect.Array, reflect.Chan, reflect.Ptr, reflect.Slice: 55 | same = same || dst.Elem() == src.Elem() 56 | case reflect.Map: 57 | same = same || (dst.Elem() == src.Elem() && dst.Key() == src.Key()) 58 | } 59 | 60 | if same && dst.Size() == src.Size() && src.Size() > 0 && src.Size() <= uintptr(len(t.sizes)) { 61 | return t.sizes[src.Size()-1] 62 | } 63 | 64 | return nil 65 | } 66 | 67 | // Set the copy function for the pair of types. 68 | func (t *CopyFuncs) Set(dst, src reflect.Type, f func(dst, src unsafe.Pointer)) { 69 | t.mu.Lock() 70 | t.funcs[funcKey{Src: src, Dst: dst}] = f 71 | t.mu.Unlock() 72 | } 73 | 74 | // Get the copy function for the pair of types, if it is not found then nil is returned. 75 | func Get(dst, src reflect.Type) func(dst, src unsafe.Pointer) { 76 | return funcs.Get(dst, src) 77 | } 78 | 79 | // Set the copy function for the pair of types. 80 | func Set(dst, src reflect.Type, f func(dst, src unsafe.Pointer)) { 81 | funcs.Set(dst, src, f) 82 | } 83 | 84 | var funcs = &CopyFuncs{ 85 | funcs: map[funcKey]func(dst, src unsafe.Pointer){}, 86 | sizes: []func(dst, src unsafe.Pointer){}, 87 | } 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Package for fast copying structs of different types 2 | 3 | [![GoDev](https://img.shields.io/static/v1?label=godev&message=reference&color=00add8)][godev] [![Go Report Card](https://goreportcard.com/badge/github.com/gotidy/copy)][goreport] [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go) 4 | 5 | [godev]: https://pkg.go.dev/github.com/gotidy/copy 6 | [goreport]: https://goreportcard.com/report/github.com/gotidy/copy 7 | 8 | This package is meant to make copying of structs to/from others structs a bit easier. 9 | 10 | Nested structures, embedded types, pointers, sql null types are supported. 11 | 12 | ## Installation 13 | 14 | ```sh 15 | go get -u github.com/gotidy/copy 16 | ``` 17 | 18 | ## Example 19 | 20 | ```go 21 | type Person struct { 22 | Name string 23 | MiddleName *string 24 | Surname string 25 | } 26 | 27 | type User struct { 28 | Person 29 | Email string 30 | Age int8 31 | Married bool 32 | } 33 | 34 | type Employee struct { 35 | Name string 36 | MiddleName string 37 | Surname string 38 | Email string 39 | Age int 40 | } 41 | 42 | src := User{ 43 | Person: Person{ 44 | Name: "John", 45 | MiddleName: nil, 46 | Surname: "Smith", 47 | }, 48 | Email: "john.smith@joy.me", 49 | Age: 33, 50 | Married: false, 51 | } 52 | dst := Employee{} 53 | 54 | copiers := copy.New() // New("json") 55 | copiers.Copy(&dst, &src) 56 | 57 | // Or more fast use case is to create the type specific copier. 58 | 59 | copier := copiers.Get(&Employee{}, &User{}) // Created once for a pair of types. 60 | copier.Copy(&dst, &src) 61 | 62 | ``` 63 | 64 | ## Alternative projects 65 | 66 | - [ulule/Deepcopier](https://github.com/ulule/deepcopier) 67 | - [jinzhu/Copier](https://github.com/jinzhu/copier) 68 | 69 | ### [Benchmark](https://github.com/gotidy/copy-bench) 70 | 71 | Benchmarks source code can be found [here](https://github.com/gotidy/copy-bench) 72 | 73 | ```sh 74 | go test -bench=. -benchmem ./... 75 | goos: darwin 76 | goarch: amd64 77 | pkg: github.com/gotidy/copy-bench 78 | BenchmarkManualCopy-12 177310519 6.92 ns/op 0 B/op 0 allocs/op 79 | BenchmarkCopiers-12 13476417 84.1 ns/op 0 B/op 0 allocs/op 80 | BenchmarkCopier-12 40226689 27.5 ns/op 0 B/op 0 allocs/op 81 | BenchmarkJinzhuCopier-12 407480 2711 ns/op 2480 B/op 34 allocs/op 82 | BenchmarkDeepcopier-12 262836 4346 ns/op 4032 B/op 73 allocs/op 83 | PASS 84 | ok github.com/gotidy/copy-bench 6.922s 85 | ``` 86 | 87 | See the [documentation][godev] for more information. 88 | 89 | ## License 90 | 91 | [Apache 2.0](https://github.com/gotidy/copy/blob/master/LICENSE) 92 | -------------------------------------------------------------------------------- /struct_copier.go: -------------------------------------------------------------------------------- 1 | // Package copy provides a copy library to make copying of structs to/from others structs a bit easier. 2 | package copy 3 | 4 | import ( 5 | "fmt" 6 | "reflect" 7 | "unsafe" 8 | 9 | "github.com/gotidy/copy/funcs" 10 | "github.com/gotidy/copy/internal/cache" 11 | ) 12 | 13 | type copierFunc = func(dst, src unsafe.Pointer) 14 | 15 | // StructCopier fills a destination from source. 16 | type StructCopier struct { 17 | BaseCopier 18 | 19 | copiers []copierFunc 20 | } 21 | 22 | func NewStructCopier(c *Copiers) *StructCopier { 23 | copier := &StructCopier{BaseCopier: NewBaseCopier(c)} 24 | return copier 25 | } 26 | 27 | func (c *StructCopier) init(dst, src reflect.Type) { 28 | c.BaseCopier.init(dst, src) 29 | 30 | srcStruct := c.cache.GetByType(src) 31 | dstStruct := c.cache.GetByType(dst) 32 | 33 | for i := 0; i < srcStruct.NumField(); i++ { 34 | srcField := srcStruct.Field(i) 35 | if dstField, ok := dstStruct.FieldByName(srcField.Name); ok { 36 | if f := c.fieldCopier(dstField, srcField); f != nil { 37 | c.copiers = append(c.copiers, f) 38 | } 39 | } 40 | } 41 | } 42 | 43 | // Copy copies the contents of src into dst. Dst and src each must be a pointer to struct. 44 | func (c *StructCopier) Copy(dst, src interface{}) { 45 | dstType, dstPtr := DataOf(dst) 46 | srcType, srcPtr := DataOf(src) 47 | 48 | if c.src.Check(srcType) { 49 | panic("source expected type " + c.src.Name + ", but has " + reflect.TypeOf(src).String()) 50 | } 51 | if c.dst.Check(dstType) { 52 | panic("destination expected type " + c.dst.Name + ", but has " + reflect.TypeOf(dst).String()) 53 | } 54 | 55 | c.copy(dstPtr, srcPtr) 56 | } 57 | 58 | func (c *StructCopier) copy(dst, src unsafe.Pointer) { 59 | for _, c := range c.copiers { 60 | c(dst, src) 61 | } 62 | } 63 | 64 | func (c *StructCopier) fieldCopier(dst, src cache.Field) copierFunc { 65 | // TODO: Unify with the slice copier, may be do it inside Copiers.get 66 | dstOffset := dst.Offset 67 | srcOffset := src.Offset 68 | copierFunc := funcs.Get(dst.Type, src.Type) 69 | if copierFunc != nil { 70 | return func(dstPtr, srcPtr unsafe.Pointer) { 71 | copierFunc(unsafe.Pointer(uintptr(dstPtr)+dstOffset), unsafe.Pointer(uintptr(srcPtr)+srcOffset)) 72 | } 73 | } 74 | 75 | // same type -> same type 76 | if src.Type == dst.Type { 77 | size := int(src.Type.Size()) 78 | 79 | return func(dstPtr, srcPtr unsafe.Pointer) { 80 | // More safe and independent from internal structs 81 | // src := reflect.NewAt(src.Type, unsafe.Pointer(uintptr(srcPtr)+src.Offset)).Elem() 82 | // dst := reflect.NewAt(dst.Type, unsafe.Pointer(uintptr(dstPtr)+dst.Offset)).Elem() 83 | // dst.Set(src) 84 | memcopy(unsafe.Pointer(uintptr(dstPtr)+dst.Offset), unsafe.Pointer(uintptr(srcPtr)+src.Offset), size) 85 | } 86 | } 87 | 88 | copier, err := c.get(dst.Type, src.Type) 89 | if err == nil { 90 | return func(dstPtr, srcPtr unsafe.Pointer) { 91 | copier.copy(unsafe.Pointer(uintptr(dstPtr)+dst.Offset), unsafe.Pointer(uintptr(srcPtr)+src.Offset)) 92 | } 93 | } 94 | 95 | if !c.options.Skip { 96 | panic(fmt.Errorf(`field «%s» of type «%s» is not assignable to field «%s» of type «%s»`, src.Name, src.Type.String(), dst.Name, dst.Type.String())) 97 | } 98 | 99 | return nil 100 | } 101 | -------------------------------------------------------------------------------- /internal/cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | "sync" 8 | ) 9 | 10 | // Field info. 11 | type Field struct { 12 | Type reflect.Type 13 | Name string 14 | Anonymous bool 15 | Offset uintptr 16 | ParentName string 17 | } 18 | 19 | // Struct fields info. 20 | type Struct struct { 21 | Fields []Field 22 | Names map[string]Field 23 | } 24 | 25 | type tagKind int 26 | 27 | const ( 28 | tagNormal = iota 29 | tagOmit 30 | tagEmbed 31 | ) 32 | 33 | func parseTag(tag string) (name string, kind tagKind) { 34 | switch tag { 35 | case "-": 36 | return "", tagOmit 37 | case "+": 38 | return "", tagEmbed 39 | } 40 | 41 | if idx := strings.Index(tag, ","); idx != -1 { 42 | return tag[:idx], tagNormal 43 | } 44 | 45 | return tag, tagNormal 46 | } 47 | 48 | // NewStruct inits the new struct info. 49 | func NewStruct(t reflect.Type, tagName string) Struct { 50 | s := Struct{Fields: make([]Field, 0, t.NumField()), Names: make(map[string]Field, t.NumField())} 51 | 52 | var traverse func(t reflect.Type, name string, offset uintptr) 53 | traverse = func(t reflect.Type, name string, offset uintptr) { 54 | for i := 0; i < t.NumField(); i++ { 55 | field := t.Field(i) 56 | if field.PkgPath != "" { 57 | continue 58 | } 59 | 60 | fi := Field{ 61 | Type: field.Type, 62 | Name: field.Name, 63 | Offset: field.Offset + offset, 64 | Anonymous: field.Anonymous && field.Type.Kind() == reflect.Struct, 65 | ParentName: name, 66 | } 67 | 68 | if tagName != "" { 69 | if tag, ok := field.Tag.Lookup(tagName); ok { 70 | s, kind := parseTag(tag) 71 | switch kind { 72 | case tagOmit: 73 | continue 74 | case tagEmbed: 75 | fi.Anonymous = field.Type.Kind() == reflect.Struct 76 | } 77 | 78 | if s != "" { 79 | fi.Name = s 80 | } 81 | } 82 | } 83 | 84 | s.Fields = append(s.Fields, fi) 85 | s.Names[fi.Name] = fi 86 | 87 | if fi.Anonymous { 88 | traverse(fi.Type, fi.Name, fi.Offset) 89 | } 90 | } 91 | } 92 | traverse(t, "", 0) 93 | 94 | return s 95 | } 96 | 97 | // Field returns a struct type's i'th field. 98 | // It panics if i is not in the range [0, NumField()). 99 | func (s Struct) Field(i int) Field { 100 | return s.Fields[i] 101 | } 102 | 103 | // FieldByName returns the struct field with the given name 104 | // and a boolean indicating if the field was found. 105 | func (s Struct) FieldByName(name string) (Field, bool) { 106 | f, ok := s.Names[name] 107 | return f, ok 108 | } 109 | 110 | // NumField returns a struct type's field count. 111 | // It panics if the type's Kind is not Struct. 112 | func (s Struct) NumField() int { 113 | return len(s.Fields) 114 | } 115 | 116 | // Cache is structs' cache. 117 | type Cache struct { 118 | mu sync.RWMutex 119 | tag string 120 | structs map[reflect.Type]Struct 121 | } 122 | 123 | // New creates structs Cache. 124 | func New(tagName string) *Cache { 125 | return &Cache{tag: tagName, structs: make(map[reflect.Type]Struct)} 126 | } 127 | 128 | // Get returns struct fields info. 129 | func (c *Cache) Get(i interface{}) Struct { 130 | t := reflect.TypeOf(i) 131 | if t.Kind() == reflect.Ptr { 132 | t = t.Elem() 133 | } 134 | 135 | return c.GetByType(t) 136 | } 137 | 138 | // GetByType returns struct fields info. 139 | func (c *Cache) GetByType(t reflect.Type) Struct { 140 | c.mu.RLock() 141 | s, ok := c.structs[t] 142 | c.mu.RUnlock() 143 | if ok { 144 | return s 145 | } 146 | 147 | if t.Kind() != reflect.Struct { 148 | panic(fmt.Errorf("type %s is not struct", t)) 149 | } 150 | 151 | s = NewStruct(t, c.tag) 152 | c.mu.Lock() 153 | c.structs[t] = s 154 | c.mu.Unlock() 155 | 156 | return s 157 | } 158 | -------------------------------------------------------------------------------- /copiers.go: -------------------------------------------------------------------------------- 1 | // Package copy provides a copy library to make copying of structs to/from others structs a bit easier. 2 | package copy 3 | 4 | import ( 5 | "fmt" 6 | "reflect" 7 | "sync" 8 | "unsafe" 9 | 10 | "github.com/gotidy/copy/internal/cache" 11 | ) 12 | 13 | const defaultTagName = "copy" 14 | 15 | type copierKey struct { 16 | Src reflect.Type 17 | Dest reflect.Type 18 | } 19 | 20 | type indirectCopierKey struct { 21 | Src Type 22 | Dest Type 23 | } 24 | 25 | // Options is Copiers parameters. 26 | type Options struct { 27 | Tag string 28 | Skip bool 29 | } 30 | 31 | // Option changes default Copiers parameters. 32 | type Option func(c *Options) 33 | 34 | // Tag set tag name. 35 | func Tag(tag string) Option { 36 | return func(o *Options) { 37 | o.Tag = tag 38 | } 39 | } 40 | 41 | // Skip nonassignable types else cause panic. 42 | func Skip() Option { 43 | return func(o *Options) { 44 | o.Skip = true 45 | } 46 | } 47 | 48 | // StructCopier fills a destination from source. 49 | type Copier interface { 50 | Copy(dst interface{}, src interface{}) 51 | } 52 | 53 | type internalCopier interface { 54 | Copier 55 | copy(dst, src unsafe.Pointer) 56 | init(dst, src reflect.Type) 57 | } 58 | 59 | // Copiers is a structs copier. 60 | type Copiers struct { 61 | cache *cache.Cache 62 | options Options 63 | 64 | mu sync.RWMutex 65 | copiers map[copierKey]internalCopier 66 | indirectCopiers map[indirectCopierKey]Copier 67 | } 68 | 69 | // New create new internalCopier. 70 | func New(options ...Option) *Copiers { 71 | var opts Options 72 | 73 | for _, option := range options { 74 | option(&opts) 75 | } 76 | 77 | return &Copiers{ 78 | cache: cache.New(opts.Tag), 79 | options: opts, 80 | copiers: make(map[copierKey]internalCopier), 81 | indirectCopiers: make(map[indirectCopierKey]Copier), 82 | } 83 | } 84 | 85 | // Prepare caches structures of src and dst. Dst and src each must be a pointer to struct. 86 | // contents is not copied. It can be used for checking ability of copying. 87 | // 88 | // c := copy.New() 89 | // c.Prepare(&dst, &src) 90 | func (c *Copiers) Prepare(dst, src interface{}) { 91 | _ = c.Get(dst, src) 92 | } 93 | 94 | // Copy copies the contents of src into dst. Dst and src each must be a pointer to struct. 95 | func (c *Copiers) Copy(dst, src interface{}) { 96 | c.Get(dst, src).Copy(dst, src) 97 | } 98 | 99 | func checkGet(copier internalCopier, err error) internalCopier { 100 | if err != nil { 101 | panic(err) 102 | } 103 | return copier 104 | } 105 | 106 | func (c *Copiers) get(dst, src reflect.Type) (internalCopier, error) { 107 | copier, ok := c.copiers[copierKey{Src: src, Dest: dst}] 108 | if ok { 109 | return copier, nil 110 | } 111 | 112 | copier = getCopier(c, dst, src) 113 | if copier == nil { 114 | return nil, fmt.Errorf("the combination of destination(%s) and source(%s) types is not supported", dst, src) 115 | } 116 | 117 | c.copiers[copierKey{Src: src, Dest: dst}] = copier 118 | 119 | copier.init(dst, src) 120 | 121 | return copier, nil 122 | } 123 | 124 | // Get Copier for a specific destination and source. 125 | func (c *Copiers) Get(dst, src interface{}) Copier { 126 | c.mu.RLock() 127 | copier, ok := c.indirectCopiers[indirectCopierKey{Dest: TypeOf(dst), Src: TypeOf(src)}] 128 | c.mu.RUnlock() 129 | if ok { 130 | return copier 131 | } 132 | 133 | c.mu.Lock() 134 | defer c.mu.Unlock() 135 | 136 | srcType := reflect.TypeOf(src) 137 | if srcType.Kind() != reflect.Ptr { 138 | panic("source must be pointer") 139 | } 140 | srcType = srcType.Elem() 141 | 142 | dstType := reflect.TypeOf(dst) 143 | if dstType.Kind() != reflect.Ptr { 144 | panic("destination must be pointer") 145 | } 146 | dstType = dstType.Elem() 147 | 148 | copier = checkGet(c.get(dstType, srcType)) 149 | 150 | c.indirectCopiers[indirectCopierKey{Dest: TypeOf(dst), Src: TypeOf(src)}] = copier 151 | 152 | return copier 153 | } 154 | 155 | // defaultCopier uses Copier with a "copy" tag. 156 | var defaultCopier = New(Tag(defaultTagName)) 157 | 158 | // Prepare caches structures of src and dst. Dst and src each must be a pointer to struct. 159 | // contents is not copied. It can be used for checking ability of copying. 160 | // 161 | // copy.Prepare(&dst, &src) 162 | func Prepare(dst, src interface{}) { 163 | defaultCopier.Prepare(dst, src) 164 | } 165 | 166 | // Copy copies the contents of src into dst. Dst and src each must be a pointer to a struct. 167 | func Copy(dst, src interface{}) { 168 | defaultCopier.Copy(dst, src) 169 | } 170 | 171 | // Get Copier for a specific destination and source. 172 | func Get(dst, src interface{}) Copier { 173 | return defaultCopier.Get(dst, src) 174 | } 175 | -------------------------------------------------------------------------------- /copier.go: -------------------------------------------------------------------------------- 1 | package copy 2 | 3 | import ( 4 | "reflect" 5 | "unsafe" 6 | 7 | "github.com/gotidy/copy/funcs" 8 | ) 9 | 10 | type TypeInfo struct { 11 | Type Type 12 | Name string 13 | } 14 | 15 | func NewTypeInfo(typ reflect.Type) TypeInfo { 16 | p := reflect.New(typ) 17 | return TypeInfo{Type: TypeOf(p.Interface()), Name: p.String()} 18 | } 19 | 20 | func (t TypeInfo) Check(typ Type) bool { 21 | return t.Type != typ 22 | } 23 | 24 | type BaseCopier struct { 25 | *Copiers 26 | 27 | dst TypeInfo 28 | src TypeInfo 29 | } 30 | 31 | func NewBaseCopier(c *Copiers) BaseCopier { 32 | return BaseCopier{Copiers: c} 33 | } 34 | 35 | func (b *BaseCopier) init(dst, src reflect.Type) { 36 | b.dst = NewTypeInfo(dst) 37 | b.src = NewTypeInfo(src) 38 | } 39 | 40 | func (b *BaseCopier) getCopierFunc(dst, src reflect.Type, dstOffset, srcOffset uintptr) copierFunc { 41 | copierFunc := funcs.Get(dst, src) 42 | if copierFunc != nil { 43 | return func(dstPtr, srcPtr unsafe.Pointer) { 44 | copierFunc(unsafe.Pointer(uintptr(dstPtr)+dstOffset), unsafe.Pointer(uintptr(srcPtr)+srcOffset)) 45 | } 46 | } 47 | 48 | // same type -> same type 49 | if src == dst { 50 | size := int(src.Size()) 51 | 52 | return func(dstPtr, srcPtr unsafe.Pointer) { 53 | // More safe and independent from internal structs 54 | // src := reflect.NewAt(src, unsafe.Pointer(uintptr(srcPtr)+src.Offset)).Elem() 55 | // dst := reflect.NewAt(dst, unsafe.Pointer(uintptr(dstPtr)+dst.Offset)).Elem() 56 | // dst.Set(src) 57 | memcopy(unsafe.Pointer(uintptr(dstPtr)+dstOffset), unsafe.Pointer(uintptr(srcPtr)+srcOffset), size) 58 | } 59 | } 60 | 61 | copier, err := b.get(dst, src) 62 | if err == nil { 63 | return func(dstPtr, srcPtr unsafe.Pointer) { 64 | copier.copy(unsafe.Pointer(uintptr(dstPtr)+dstOffset), unsafe.Pointer(uintptr(srcPtr)+srcOffset)) 65 | } 66 | } 67 | 68 | return nil 69 | } 70 | 71 | type ValueToPValueCopier struct { 72 | BaseCopier 73 | 74 | structCopier func(dst, src unsafe.Pointer) 75 | size int 76 | } 77 | 78 | func NewValueToPValueCopier(c *Copiers) *ValueToPValueCopier { 79 | copier := &ValueToPValueCopier{BaseCopier: NewBaseCopier(c)} 80 | return copier 81 | } 82 | 83 | func (c *ValueToPValueCopier) init(dst, src reflect.Type) { 84 | c.BaseCopier.init(dst, src) 85 | dst = dst.Elem() // *struct -> struct 86 | c.structCopier = checkGet(c.get(dst, src)).copy // Get struct copier for struct -> struct 87 | c.size = int(dst.Size()) 88 | } 89 | 90 | func (c *ValueToPValueCopier) Copy(dst, src interface{}) { 91 | dstType, dstPtr := DataOf(dst) 92 | srcType, srcPtr := DataOf(src) 93 | 94 | if c.src.Check(srcType) { 95 | panic("source expected type " + c.src.Name + ", but has " + reflect.TypeOf(src).String()) 96 | } 97 | if c.dst.Check(dstType) { 98 | panic("destination expected type " + c.dst.Name + ", but has " + reflect.TypeOf(dst).String()) 99 | } 100 | 101 | c.copy(dstPtr, srcPtr) 102 | } 103 | 104 | func (c *ValueToPValueCopier) copy(dst, src unsafe.Pointer) { 105 | dstFieldPtr := (**struct{})(dst) 106 | if *dstFieldPtr == nil { 107 | *dstFieldPtr = (*struct{})(alloc(c.size)) 108 | } 109 | 110 | c.structCopier(unsafe.Pointer(*dstFieldPtr), src) 111 | } 112 | 113 | type PValueToValueCopier struct { 114 | BaseCopier 115 | 116 | structCopier func(dst, src unsafe.Pointer) 117 | } 118 | 119 | func NewPValueToValueCopier(c *Copiers) *PValueToValueCopier { 120 | copier := &PValueToValueCopier{BaseCopier: NewBaseCopier(c)} 121 | return copier 122 | } 123 | 124 | func (c *PValueToValueCopier) init(dst, src reflect.Type) { 125 | c.BaseCopier.init(dst, src) 126 | src = src.Elem() // *struct -> struct 127 | c.structCopier = checkGet(c.get(dst, src)).copy // Get struct copier for struct -> struct 128 | } 129 | 130 | func (c *PValueToValueCopier) Copy(dst, src interface{}) { 131 | dstType, dstPtr := DataOf(dst) 132 | srcType, srcPtr := DataOf(src) 133 | 134 | if c.src.Check(srcType) { 135 | panic("source expected type " + c.src.Name + ", but has " + reflect.TypeOf(src).String()) 136 | } 137 | if c.dst.Check(dstType) { 138 | panic("destination expected type " + c.dst.Name + ", but has " + reflect.TypeOf(dst).String()) 139 | } 140 | 141 | c.copy(dstPtr, srcPtr) 142 | } 143 | 144 | func (c *PValueToValueCopier) copy(dst, src unsafe.Pointer) { 145 | srcFieldPtr := (**struct{})(src) 146 | if *srcFieldPtr == nil { 147 | return 148 | } 149 | 150 | c.structCopier(dst, unsafe.Pointer(*srcFieldPtr)) 151 | } 152 | 153 | type PValueToPValueCopier struct { 154 | BaseCopier 155 | 156 | structCopier func(dst, src unsafe.Pointer) 157 | size int 158 | } 159 | 160 | func NewPValueToPValueCopier(c *Copiers) *PValueToPValueCopier { 161 | copier := &PValueToPValueCopier{BaseCopier: NewBaseCopier(c)} 162 | return copier 163 | } 164 | 165 | func (c *PValueToPValueCopier) init(dst, src reflect.Type) { 166 | c.BaseCopier.init(dst, src) 167 | dst = dst.Elem() // *struct -> struct 168 | src = src.Elem() // *struct -> struct 169 | c.structCopier = checkGet(c.get(dst, src)).copy // Get struct copier for struct -> struct 170 | c.size = int(dst.Size()) 171 | } 172 | 173 | func (c *PValueToPValueCopier) Copy(dst, src interface{}) { 174 | dstType, dstPtr := DataOf(dst) 175 | srcType, srcPtr := DataOf(src) 176 | 177 | if c.src.Check(srcType) { 178 | panic("source expected type " + c.src.Name + ", but has " + reflect.TypeOf(src).String()) 179 | } 180 | if c.dst.Check(dstType) { 181 | panic("destination expected type " + c.dst.Name + ", but has " + reflect.TypeOf(dst).String()) 182 | } 183 | 184 | c.copy(dstPtr, srcPtr) 185 | } 186 | 187 | func (c *PValueToPValueCopier) copy(dst, src unsafe.Pointer) { 188 | srcFieldPtr := (**struct{})(src) 189 | if *srcFieldPtr == nil { 190 | return 191 | } 192 | 193 | dstFieldPtr := (**struct{})(dst) 194 | if *dstFieldPtr == nil { 195 | *dstFieldPtr = (*struct{})(alloc(c.size)) 196 | } 197 | 198 | c.structCopier(unsafe.Pointer(*dstFieldPtr), unsafe.Pointer(*srcFieldPtr)) 199 | } 200 | -------------------------------------------------------------------------------- /funcs/funcs_test.go: -------------------------------------------------------------------------------- 1 | package funcs 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "database/sql" 7 | "database/sql/driver" 8 | "reflect" 9 | "testing" 10 | "time" 11 | "unsafe" 12 | 13 | "github.com/gotidy/ptr" 14 | ) 15 | 16 | func TestBytesCopiers(t *testing.T) { 17 | src := make([]byte, 100) 18 | _, err := rand.Read(src) 19 | if err != nil { 20 | t.Fatalf("rand: %s", err) 21 | } 22 | for i := 1; i <= len(src); i++ { 23 | dst := make([]byte, i) 24 | src := src[:i] 25 | copier := Get(reflect.TypeOf(dst), reflect.TypeOf(src)) 26 | copier(unsafe.Pointer(reflect.ValueOf(&dst).Pointer()), unsafe.Pointer(reflect.ValueOf(&src).Pointer())) 27 | if !bytes.Equal(dst, src) { 28 | t.Fatalf("a destination with len %d is not equal a source", i) 29 | } 30 | } 31 | } 32 | 33 | func TestMemCopiers(t *testing.T) { 34 | src := make([]byte, len(funcs.sizes)) 35 | _, err := rand.Read(src) 36 | if err != nil { 37 | t.Fatalf("rand: %s", err) 38 | } 39 | for i := 1; i <= len(src); i++ { 40 | dst := make([]byte, i) 41 | src := src[:i] 42 | typ := reflect.ArrayOf(i, reflect.TypeOf(uint8(0))) 43 | copier := Get(typ, typ) 44 | copier(unsafe.Pointer(reflect.ValueOf(dst).Pointer()), unsafe.Pointer(reflect.ValueOf(src).Pointer())) 45 | if !bytes.Equal(dst, src) { 46 | t.Fatalf("a destination with len %d is not equal a source", i) 47 | } 48 | } 49 | } 50 | 51 | func TestTypesCopiers(t *testing.T) { 52 | b := []byte("COVID-21") 53 | testValues := make(map[reflect.Type]reflect.Value) 54 | for _, v := range []interface{}{ 55 | ptr.Int(10), 56 | ptr.Int8(10), 57 | ptr.Int16(10), 58 | ptr.Int32(10), 59 | ptr.Int64(10), 60 | ptr.UInt(10), 61 | ptr.UInt8(10), 62 | ptr.UInt16(10), 63 | ptr.UInt32(10), 64 | ptr.UInt64(10), 65 | ptr.Float32(10.0), 66 | ptr.Float64(10.0), 67 | ptr.Complex64(10.0), 68 | ptr.Complex128(10.0), 69 | ptr.Bool(true), 70 | ptr.Duration(time.Second), 71 | ptr.Time(time.Date(2021, 2, 18, 16, 0, 1, 0, time.UTC)), 72 | ptr.String("COVID-21"), 73 | &b, 74 | &sql.NullInt32{Int32: 10, Valid: true}, 75 | &sql.NullBool{Bool: true, Valid: true}, 76 | &sql.NullInt64{Int64: 10, Valid: true}, 77 | &sql.NullFloat64{Float64: 10.0, Valid: true}, 78 | &sql.NullString{String: "COVID-21", Valid: true}, 79 | &sql.NullTime{Time: time.Date(2021, 2, 18, 16, 0, 1, 0, time.UTC), Valid: true}, 80 | } { 81 | val := reflect.ValueOf(v) 82 | testValues[val.Elem().Type()] = val 83 | 84 | valP := reflect.New(val.Type()) 85 | valP.Elem().Set(val) 86 | testValues[valP.Elem().Type()] = valP 87 | } 88 | 89 | for key, copier := range funcs.funcs { 90 | // var dst reflect.Value 91 | 92 | src, ok := testValues[key.Src] 93 | if !ok { 94 | continue 95 | } 96 | 97 | dst := reflect.New(key.Dst) 98 | 99 | t.Logf("Src key «%s»; src «%#v»", key.Src, src) 100 | t.Logf("Dst key «%s»; dst «%#v»", key.Dst, dst) 101 | 102 | set := func(dst, src reflect.Value) { 103 | copier(unsafe.Pointer(dst.Pointer()), unsafe.Pointer(src.Pointer())) 104 | 105 | dst = dst.Elem() 106 | src = src.Elem() 107 | t.Logf("src «%#v»", src) 108 | t.Logf("dst «%#v»", dst) 109 | 110 | if key.Dst.Kind() == reflect.Ptr { 111 | dst = dst.Elem() 112 | } 113 | dstI := dst.Interface() 114 | // Destination is sql.Null 115 | if v, ok := dstI.(driver.Valuer); ok { 116 | dstI, _ = v.Value() 117 | dst = reflect.ValueOf(dstI) 118 | } 119 | 120 | if key.Src.Kind() == reflect.Ptr { 121 | src = src.Elem() 122 | } 123 | 124 | srcI := src.Interface() 125 | // Source is sql.Null 126 | if v, ok := srcI.(driver.Valuer); ok { 127 | srcI, _ = v.Value() 128 | src = reflect.ValueOf(srcI) 129 | } 130 | 131 | src = src.Convert(dst.Type()) 132 | srcI = src.Interface() 133 | 134 | if !reflect.DeepEqual(dstI, srcI) { 135 | t.Errorf("want «%v» got «%v»", srcI, dstI) 136 | } 137 | } 138 | 139 | set(dst, src) 140 | if elem := dst.Elem(); elem.Kind() == reflect.Ptr { 141 | elem.Set(reflect.New(elem.Type().Elem())) 142 | set(dst, src) 143 | } 144 | } 145 | } 146 | 147 | func isNullType(v reflect.Value) bool { 148 | _, ok := v.Interface().(driver.Valuer) 149 | return ok 150 | } 151 | 152 | func nullTypeIface(v reflect.Value) (interface{}, bool) { 153 | if v, ok := v.Interface().(driver.Valuer); ok { 154 | i, _ := v.Value() 155 | return i, true 156 | } 157 | return nil, false 158 | } 159 | 160 | // Test copy (pointer | Null) -> (pointer, Null). 161 | func TestTypesCopiersNil(t *testing.T) { 162 | b := []byte("COVID-21") 163 | testValues := make(map[reflect.Type]reflect.Value) 164 | for _, v := range []interface{}{ 165 | ptr.Int(10), 166 | ptr.Int8(10), 167 | ptr.Int16(10), 168 | ptr.Int32(10), 169 | ptr.Int64(10), 170 | ptr.UInt(10), 171 | ptr.UInt8(10), 172 | ptr.UInt16(10), 173 | ptr.UInt32(10), 174 | ptr.UInt64(10), 175 | ptr.Float32(10.0), 176 | ptr.Float64(10.0), 177 | ptr.Complex64(10.0), 178 | ptr.Complex128(10.0), 179 | ptr.Bool(true), 180 | ptr.Duration(time.Second), 181 | ptr.Time(time.Date(2021, 2, 18, 16, 0, 1, 0, time.UTC)), 182 | ptr.String("COVID-21"), 183 | &b, 184 | &sql.NullInt32{Int32: 10, Valid: true}, 185 | &sql.NullBool{Bool: true, Valid: true}, 186 | &sql.NullInt64{Int64: 10, Valid: true}, 187 | &sql.NullFloat64{Float64: 10.0, Valid: true}, 188 | &sql.NullString{String: "COVID-21", Valid: true}, 189 | &sql.NullTime{Time: time.Date(2021, 2, 18, 16, 0, 1, 0, time.UTC), Valid: true}, 190 | } { 191 | val := reflect.ValueOf(v) 192 | testValues[val.Elem().Type()] = val 193 | 194 | valP := reflect.New(val.Type()) 195 | valP.Elem().Set(val) 196 | testValues[valP.Elem().Type()] = valP 197 | } 198 | 199 | for key, copier := range funcs.funcs { 200 | // Initialize dst by test value 201 | dst, ok := testValues[key.Dst] 202 | if !ok { 203 | continue 204 | } 205 | 206 | dst = dst.Elem() 207 | // src = src.Elem() 208 | 209 | src := reflect.New(key.Src).Elem() 210 | t.Logf("src (%s)«%#v»", src.Type(), src) 211 | t.Logf("dst (%s)«%#v»", dst.Type(), dst) 212 | 213 | if dstIsNull, srcIsNull := isNullType(dst), isNullType(src); // 214 | !(key.Dst.Kind() == reflect.Ptr || dstIsNull) || // Skip if dst is not pointer or Null type. 215 | !(key.Src.Kind() == reflect.Ptr || srcIsNull) || // Skip if src is not pointer or Null type. 216 | (dstIsNull && srcIsNull) { // Skip if dst and src Null type together. 217 | continue 218 | } 219 | 220 | t.Logf("Src key «%s»; src «%#v»", key.Src, src) 221 | t.Logf("Dst key «%s»; dst «%#v»", key.Dst, dst) 222 | 223 | copier(unsafe.Pointer(dst.Addr().Pointer()), unsafe.Pointer(src.Addr().Pointer())) 224 | 225 | t.Logf("src «%#v»", src) 226 | t.Logf("dst «%#v»", dst) 227 | 228 | if i, ok := nullTypeIface(dst); ok { 229 | if i != nil { 230 | t.Errorf("want «%v» got «%v»", src, i) 231 | } 232 | } else { 233 | if !dst.IsNil() { 234 | t.Errorf("want «%v» got «%v»", src, i) 235 | } 236 | } 237 | } 238 | } 239 | 240 | func TestSet(t *testing.T) { 241 | Set(reflect.TypeOf(int(0)), reflect.TypeOf(int(0)), func(dst, src unsafe.Pointer) { 242 | *(*int)(unsafe.Pointer(dst)) = int(*(*int)(unsafe.Pointer(src))) 243 | }) 244 | } 245 | 246 | func TestGet(t *testing.T) { 247 | copier := Get(reflect.TypeOf(struct{ I int }{I: 1}), reflect.TypeOf([]byte{2})) 248 | if copier != nil { 249 | t.Error("should return nil when types are incompatible") 250 | } 251 | 252 | copier = Get(reflect.TypeOf(map[string]string{}), reflect.TypeOf(map[string]string{})) 253 | if copier == nil { 254 | t.Error("Get(map, map) should not return nil") 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /funcs/gen.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | package main 4 | 5 | import ( 6 | "log" 7 | "os" 8 | "strings" 9 | "text/template" 10 | "time" 11 | ) 12 | 13 | const fileTemplate = `// Code generated by go generate; DO NOT EDIT. 14 | // This file was generated by robots at 15 | // {{ .Timestamp }} 16 | package funcs 17 | 18 | import ( 19 | "database/sql" 20 | "time" 21 | "unsafe" 22 | ) 23 | 24 | func init() { 25 | funcs = &CopyFuncs{ 26 | funcs: map[funcKey]func(dst, src unsafe.Pointer){ 27 | {{- range $types :=.Types}}{{range $dst := $types}}{{range $src := $types}} 28 | // {{$src}} to {{$dst}} 29 | {Src: typeOf({{$src}}({{default $src}})), Dst: typeOf({{$dst}}({{default $dst}}))}: copy{{title $src}}To{{title $dst}}, 30 | {Src: typeOfPointer({{$src}}({{default $src}})), Dst: typeOf({{$dst}}({{default $dst}}))}: copyP{{title $src}}To{{title $dst}}, 31 | {Src: typeOf({{$src}}({{default $src}})), Dst: typeOfPointer({{$dst}}({{default $dst}}))}: copy{{title $src}}ToP{{title $dst}}, 32 | {Src: typeOfPointer({{$src}}({{default $src}})), Dst: typeOfPointer({{$dst}}({{default $dst}}))}: copyP{{title $src}}ToP{{title $dst}}, 33 | {{- end}}{{end}}{{end}} 34 | 35 | // SQL Null types 36 | {{- range $types :=.NullTypes}}{{range $null := $types.Nulls}}{{range $type := $types.Types}} 37 | // {{$null}} to/from {{$type}} 38 | {Src: typeOf({{$null}}{}), Dst: typeOf({{$type}}({{default $type}}))}: copy{{title $null}}To{{title $type}}, 39 | {Src: typeOf({{$type}}({{default $type}})), Dst: typeOf({{$null}}{})}: copy{{title $type}}To{{title $null}}, 40 | {Src: typeOf({{$null}}{}), Dst: typeOfPointer({{$type}}({{default $type}}))}: copy{{title $null}}ToP{{title $type}}, 41 | {Src: typeOfPointer({{$type}}({{default $type}})), Dst: typeOf({{$null}}{})}: copyP{{title $type}}To{{title $null}}, 42 | {{- end}}{{end}}{{end}} 43 | }, 44 | sizes: []func(dst, src unsafe.Pointer){ 45 | {{range $size := $.Sizes -}} 46 | copy{{$size}}, 47 | {{- end}} 48 | }, 49 | } 50 | } 51 | {{range $types :=.Types}}{{range $dst := $types}}{{range $src := $types}} 52 | 53 | // {{$src}} to {{$dst}} 54 | 55 | func copy{{title $src}}To{{title $dst}}(dst, src unsafe.Pointer) { 56 | *(*{{$dst}})(unsafe.Pointer(dst)) = {{$dst}}(*(*{{$src}})(unsafe.Pointer(src))) 57 | } 58 | 59 | func copyP{{title $src}}To{{title $dst}}(dst, src unsafe.Pointer) { 60 | var v {{$dst}} 61 | if p := *(**{{$src}})(unsafe.Pointer(src)); p != nil { 62 | v = {{$dst}}(*p) 63 | } 64 | *(*{{$dst}})(unsafe.Pointer(dst)) = v 65 | } 66 | 67 | func copy{{title $src}}ToP{{title $dst}}(dst, src unsafe.Pointer) { 68 | v := {{$dst}}(*(*{{$src}})(unsafe.Pointer(src))) 69 | p := (**{{$dst}})(unsafe.Pointer(dst)) 70 | if p := *p; p != nil { 71 | *p = v 72 | return 73 | } 74 | *p = &v 75 | } 76 | 77 | func copyP{{title $src}}ToP{{title $dst}}(dst, src unsafe.Pointer) { 78 | pSrc := (**{{$src}})(unsafe.Pointer(src)) 79 | pDst := (**{{$dst}})(unsafe.Pointer(dst)) 80 | if *pSrc == nil { 81 | *pDst = nil 82 | return 83 | } 84 | 85 | v := {{$dst}}(**pSrc) 86 | if p := *pDst; p != nil { 87 | *p = v 88 | return 89 | } 90 | *pDst = &v 91 | } 92 | 93 | {{- end}}{{end}}{{end}} 94 | 95 | {{range $types :=.NullTypes}}{{range $null := $types.Nulls}}{{range $type := $types.Types}} 96 | 97 | {{$src := $null}}{{$dst := $type -}} 98 | {{/* Copy NULL to {{$dst}} */}} 99 | func copy{{title $src}}To{{title $dst}}(dst, src unsafe.Pointer) { 100 | null := *(*{{$src}})(unsafe.Pointer(src)) 101 | *(*{{$dst}})(unsafe.Pointer(dst)) = {{$dst}}(null.{{nullField $src}}) 102 | } 103 | 104 | {{$src := $type}}{{$dst := $null -}} 105 | {{/* Copy {{$src}} to NULL */}} 106 | func copy{{title $src}}To{{title $dst}}(dst, src unsafe.Pointer) { 107 | *(*{{$dst}})(unsafe.Pointer(dst)) = {{$dst}}{ 108 | {{nullField $dst}}: {{nullFieldType $dst}}(*(*{{$src}})(unsafe.Pointer(src))), 109 | Valid: true, 110 | } 111 | } 112 | 113 | {{$src := $type}}{{$dst := $null -}} 114 | {{/* Copy *{{$src}} to NULL */}} 115 | func copyP{{title $src}}To{{title $dst}}(dst, src unsafe.Pointer) { 116 | var v {{$dst}} 117 | if p := *(**{{$src}})(unsafe.Pointer(src)); p != nil { 118 | v.{{nullField $dst}} = {{nullFieldType $dst}}(*p) 119 | v.Valid = true 120 | } 121 | *(*{{$dst}})(unsafe.Pointer(dst)) = v 122 | } 123 | 124 | {{$src := $null}}{{$dst := $type -}} 125 | {{/* Copy NULL to *{{$dst}} */}} 126 | func copy{{title $src}}ToP{{title $dst}}(dst, src unsafe.Pointer) { 127 | null := *(*{{$src}})(unsafe.Pointer(src)) 128 | p := (**{{$dst}})(unsafe.Pointer(dst)) 129 | if !null.Valid { 130 | *p = nil 131 | return 132 | } 133 | v := {{$dst}}(null.{{nullField $src}}) 134 | if p := *p; p != nil { 135 | *p = v 136 | return 137 | } 138 | *p = &v 139 | } 140 | 141 | {{- end}}{{end}}{{end}} 142 | 143 | // Memcopy funcs 144 | {{- range $size := $.Sizes}} 145 | func copy{{$size}}(dst, src unsafe.Pointer) { 146 | *(*[{{$size}}]byte)(unsafe.Pointer(dst)) = *(*[{{$size}}]byte)(unsafe.Pointer(src)) 147 | } 148 | {{end}} 149 | ` 150 | 151 | const maxBlockSize = 256 152 | 153 | func title(s string) string { 154 | if strings.HasPrefix(s, "[]") { 155 | s = strings.TrimPrefix(s, "[]") + "s" 156 | } 157 | 158 | if parts := strings.Split(s, "."); len(parts) > 1 { 159 | s = parts[len(parts)-1] 160 | } 161 | 162 | return strings.Title(s) 163 | } 164 | 165 | func nullField(s string) string { 166 | return strings.TrimPrefix(s, "sql.Null") 167 | } 168 | 169 | func nullFieldType(s string) string { 170 | switch s := nullField(s); s { 171 | case "Time": 172 | return "time.Time" 173 | default: 174 | return strings.ToLower(s) 175 | } 176 | } 177 | 178 | func defaultValue(t string) string { 179 | switch t { 180 | case "bool": 181 | return "false" 182 | case "time.Time": 183 | return "time.Time{}" 184 | case "string": 185 | return `""` 186 | case "[]byte": 187 | return "nil" 188 | default: 189 | return "0" 190 | } 191 | } 192 | 193 | func createFile(path string) *os.File { 194 | file, err := os.Create(path) 195 | if err != nil { 196 | log.Fatalf(`unable to open file "%s": %s`, path, err) 197 | } 198 | 199 | return file 200 | } 201 | 202 | func main() { 203 | var data = struct { 204 | Timestamp time.Time 205 | Types [][]string 206 | NullTypes []struct { 207 | Nulls []string 208 | Types []string 209 | } 210 | Sizes []int 211 | }{ 212 | Timestamp: time.Now().UTC(), 213 | Types: [][]string{ 214 | { 215 | "int", "int8", "int16", "int32", "int64", 216 | "uint", "uint8", "uint16", "uint32", "uint64", 217 | }, 218 | {"float32", "float64"}, 219 | {"bool"}, 220 | {"complex64", "complex128"}, 221 | {"string", "[]byte"}, 222 | {"time.Time"}, 223 | {"time.Duration"}, 224 | }, 225 | NullTypes: []struct { 226 | Nulls []string 227 | Types []string 228 | }{ 229 | { 230 | Nulls: []string{"sql.NullInt32", "sql.NullInt64"}, 231 | Types: []string{ 232 | "int", "int8", "int16", "int32", "int64", 233 | "uint", "uint8", "uint16", "uint32", "uint64", 234 | }, 235 | }, 236 | { 237 | Nulls: []string{"sql.NullFloat64"}, 238 | Types: []string{"float32", "float64"}, 239 | }, 240 | { 241 | Nulls: []string{"sql.NullBool"}, 242 | Types: []string{"bool"}, 243 | }, 244 | { 245 | Nulls: []string{"sql.NullString"}, 246 | Types: []string{"string", "[]byte"}, 247 | }, 248 | { 249 | Nulls: []string{"sql.NullTime"}, 250 | Types: []string{"time.Time"}, 251 | }, 252 | }, 253 | } 254 | 255 | for i := 1; i <= maxBlockSize; i++ { 256 | data.Sizes = append(data.Sizes, i) 257 | } 258 | 259 | funcMap := template.FuncMap{ 260 | "title": title, 261 | "nullField": nullField, 262 | "nullFieldType": nullFieldType, 263 | "default": defaultValue, 264 | } 265 | 266 | tmpl, err := template.New("template").Funcs(funcMap).Parse(fileTemplate) 267 | if err != nil { 268 | log.Fatalf("parsing: %s", err) 269 | } 270 | 271 | file := createFile("funcs.gen.go") 272 | defer file.Close() 273 | 274 | err = tmpl.Execute(file, data) 275 | if err != nil { 276 | log.Fatalf("execution: %s", err) 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2020 Evgeny Safonov 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /copier_test.go: -------------------------------------------------------------------------------- 1 | package copy 2 | 3 | import ( 4 | "encoding/json" 5 | "sync" 6 | "testing" 7 | ) 8 | 9 | func trim(b []rune) []rune { 10 | if len(b) > 20 { 11 | return b[0:20] 12 | } 13 | return b 14 | } 15 | 16 | func diff(t *testing.T, prefix string, a, b []byte) { 17 | ar := []rune(string(a)) 18 | br := []rune(string(b)) 19 | 20 | l := len(ar) 21 | if l < len(br) { 22 | l = len(br) 23 | } 24 | 25 | for i := 0; i < l; i++ { 26 | if i >= len(ar) || i >= len(br) || ar[i] != br[i] { 27 | j := i - 10 28 | if j < 0 { 29 | j = 0 30 | } 31 | 32 | t.Errorf(prefix+": diverge at %d: «%s» vs «%s»", i, string(trim(ar[j:])), string(trim(br[j:]))) 33 | return 34 | } 35 | } 36 | } 37 | 38 | func equal(t *testing.T, actual, expected interface{}) { 39 | actualData, err := json.Marshal(actual) 40 | if err != nil { 41 | t.Fatalf("equal: %s", err) 42 | } 43 | 44 | t.Logf("actual: %s", string(actualData)) 45 | 46 | expectedData, err := json.Marshal(expected) 47 | if err != nil { 48 | t.Fatalf("equal: %s", err) 49 | } 50 | 51 | t.Logf("expected: %s", string(expectedData)) 52 | 53 | diff(t, "actual and expected is not equal", actualData, expectedData) 54 | } 55 | 56 | func TestCopiers_Copy(t *testing.T) { 57 | type internal1 struct { 58 | I int 59 | } 60 | 61 | type internal2 struct { 62 | I int 63 | } 64 | 65 | type Embedded struct { 66 | E string 67 | } 68 | 69 | type testStruct1 struct { 70 | Embedded 71 | S string 72 | I int 73 | BB []bool 74 | V internal1 75 | } 76 | 77 | type testStruct2 struct { 78 | Embedded 79 | S string 80 | I int 81 | BB []bool 82 | V internal2 83 | } 84 | 85 | src := testStruct1{ 86 | Embedded: Embedded{ 87 | E: "embedded", 88 | }, 89 | S: "string", 90 | I: 10, 91 | BB: []bool{true, false}, 92 | V: internal1{I: 5}, 93 | } 94 | dst := testStruct2{} 95 | 96 | Prepare(&dst, &src) 97 | Copy(&dst, &src) 98 | equal(t, dst, src) 99 | } 100 | 101 | func TestCopier_Copy(t *testing.T) { 102 | type internal1 struct { 103 | I int 104 | } 105 | 106 | type internal2 struct { 107 | I int 108 | } 109 | 110 | type Embedded struct { 111 | E string 112 | } 113 | 114 | type testStruct1 struct { 115 | Embedded 116 | S string 117 | I int 118 | BB []bool 119 | A [500]byte 120 | V internal1 121 | } 122 | 123 | type testStruct2 struct { 124 | Embedded 125 | S string 126 | I int 127 | BB []bool 128 | A [500]byte 129 | V internal2 130 | } 131 | 132 | src := testStruct1{ 133 | Embedded: Embedded{ 134 | E: "embedded", 135 | }, 136 | S: "string", 137 | I: 10, 138 | BB: []bool{true, false}, 139 | A: [500]byte{5}, 140 | V: internal1{I: 5}, 141 | } 142 | dst := testStruct2{} 143 | 144 | New().Get(&dst, &src).Copy(&dst, &src) 145 | equal(t, dst, src) 146 | } 147 | 148 | func TestCopier_Struct(t *testing.T) { 149 | v := struct{}{} 150 | func() { 151 | defer func() { 152 | if recover() == nil { 153 | t.Error("must panic on non struct source") 154 | } 155 | }() 156 | Copy(&v, new(int)) 157 | }() 158 | func() { 159 | defer func() { 160 | if recover() == nil { 161 | t.Error("must panic on non struct destination") 162 | } 163 | }() 164 | Copy(new(int), &v) 165 | }() 166 | 167 | func() { 168 | defer func() { 169 | if recover() == nil { 170 | t.Error("must panic on non struct source") 171 | } 172 | }() 173 | Get(&v, new(int)) 174 | }() 175 | func() { 176 | defer func() { 177 | if recover() == nil { 178 | t.Error("must panic on non struct destination") 179 | } 180 | }() 181 | Get(new(int), &v) 182 | }() 183 | } 184 | 185 | func TestCopier_Pointer(t *testing.T) { 186 | v := struct{}{} 187 | func() { 188 | defer func() { 189 | if recover() == nil { 190 | t.Error("must panic on non pointer source") 191 | } 192 | }() 193 | Copy(&v, v) 194 | }() 195 | func() { 196 | defer func() { 197 | if recover() == nil { 198 | t.Error("must panic on non pointer destination") 199 | } 200 | }() 201 | Copy(v, &v) 202 | }() 203 | 204 | c := New().Get(&v, &v) 205 | func() { 206 | defer func() { 207 | if recover() == nil { 208 | t.Error("must panic on when source pointer is nil") 209 | } 210 | }() 211 | c.Copy(&v, nil) 212 | }() 213 | func() { 214 | defer func() { 215 | if recover() == nil { 216 | t.Error("must panic on when destination pointer is nil") 217 | } 218 | }() 219 | Copy(nil, &v) 220 | }() 221 | } 222 | 223 | func TestCopier_Copy_CheckParams(t *testing.T) { 224 | v := struct{ i int }{} 225 | pV := &v 226 | vWrong := struct{}{} 227 | pVWrong := &vWrong 228 | 229 | copy := func(c Copier, dst, src interface{}) { 230 | defer func() { 231 | if recover() == nil { 232 | t.Error("must panic on when parameters types does not match the copier types") 233 | } 234 | }() 235 | c.Copy(dst, src) 236 | } 237 | 238 | copiers := New() 239 | 240 | // Check src parameter 241 | copy(copiers.Get(&v, &v), &vWrong, &v) 242 | copy(copiers.Get(&pV, &v), &pVWrong, &v) 243 | copy(copiers.Get(&v, &pV), &vWrong, &pV) 244 | copy(copiers.Get(&pV, &pV), &pVWrong, &pV) 245 | // Check dst parameter 246 | copy(copiers.Get(&v, &v), &v, &vWrong) 247 | copy(copiers.Get(&pV, &v), &pV, &vWrong) 248 | copy(copiers.Get(&v, &pV), &v, &pVWrong) 249 | copy(copiers.Get(&pV, &pV), &pV, &pVWrong) 250 | } 251 | 252 | func TestCopier_Skip(t *testing.T) { 253 | src := struct{ S string }{S: "string"} 254 | dst := struct{ S int }{} 255 | 256 | func() { 257 | defer func() { 258 | if recover() == nil { 259 | t.Error("must panic when fields with the same name are of different types") 260 | } 261 | }() 262 | New().Get(&dst, &src) 263 | }() 264 | 265 | func() { 266 | defer func() { 267 | if err := recover(); err != nil { 268 | t.Errorf("do not must panic with Skip option when fields with the same name are of different types: %s", err) 269 | } 270 | }() 271 | New(Skip()).Get(&dst, &src) 272 | }() 273 | } 274 | 275 | func TestCopier_Expand(t *testing.T) { 276 | type Expanded struct { 277 | E string 278 | } 279 | 280 | type testStruct1 struct { 281 | Exp Expanded `copy:"+"` 282 | } 283 | 284 | type testStruct2 struct { 285 | E string 286 | } 287 | 288 | src := testStruct1{ 289 | Exp: Expanded{ 290 | E: "Expanded", 291 | }, 292 | } 293 | dst := testStruct2{} 294 | 295 | Get(&dst, &src).Copy(&dst, &src) 296 | if src.Exp.E != dst.E { 297 | t.Errorf("want «%s» got «%s»", src.Exp.E, dst.E) 298 | } 299 | } 300 | 301 | func TestCopier_StructPtr(t *testing.T) { 302 | type internal0 struct { 303 | I int 304 | } 305 | 306 | type testStruct0 struct { 307 | V internal0 308 | } 309 | 310 | type internal1 struct { 311 | I int 312 | } 313 | 314 | type testStruct1 struct { 315 | V internal1 316 | } 317 | 318 | type internal2 struct { 319 | I int 320 | } 321 | 322 | type testStruct2 struct { 323 | V *internal2 324 | } 325 | 326 | type internal3 struct { 327 | I int 328 | } 329 | 330 | type testStruct3 struct { 331 | V *internal3 332 | } 333 | 334 | type test struct { 335 | src interface{} 336 | dst interface{} 337 | expected interface{} 338 | } 339 | tests := []test{ 340 | // struct -> struct 341 | { 342 | src: &testStruct0{V: internal0{I: 5}}, 343 | dst: &testStruct1{V: internal1{I: 0}}, 344 | expected: &testStruct1{V: internal1{I: 5}}, 345 | }, 346 | // struct -> *struct 347 | { 348 | src: &testStruct1{V: internal1{I: 5}}, 349 | dst: &testStruct2{V: nil}, 350 | expected: &testStruct2{V: &internal2{I: 5}}, 351 | }, 352 | { 353 | src: &testStruct1{V: internal1{I: 5}}, 354 | dst: &testStruct2{V: &internal2{I: 0}}, 355 | expected: &testStruct2{V: &internal2{I: 5}}, 356 | }, 357 | // *struct -> struct 358 | { 359 | src: &testStruct2{V: &internal2{I: 5}}, 360 | dst: &testStruct1{V: internal1{I: 0}}, 361 | expected: &testStruct1{V: internal1{I: 5}}, 362 | }, 363 | { 364 | src: &testStruct2{V: nil}, 365 | dst: &testStruct1{V: internal1{I: 5}}, 366 | expected: &testStruct1{V: internal1{I: 5}}, 367 | }, 368 | // *struct -> *struct 369 | { 370 | src: &testStruct2{V: nil}, 371 | dst: &testStruct3{V: nil}, 372 | expected: &testStruct3{V: nil}, 373 | }, 374 | { 375 | src: &testStruct2{V: nil}, 376 | dst: &testStruct3{V: &internal3{I: 5}}, 377 | expected: &testStruct3{V: &internal3{I: 5}}, 378 | }, 379 | { 380 | src: &testStruct2{V: &internal2{I: 5}}, 381 | dst: &testStruct3{V: &internal3{I: 0}}, 382 | expected: &testStruct3{V: &internal3{I: 5}}, 383 | }, 384 | { 385 | src: &testStruct2{V: &internal2{I: 5}}, 386 | dst: &testStruct3{V: nil}, 387 | expected: &testStruct3{V: &internal3{I: 5}}, 388 | }, 389 | } 390 | 391 | for _, test := range tests { 392 | New().Get(test.dst, test.src).Copy(test.dst, test.src) 393 | equal(t, test.dst, test.expected) 394 | // if !reflect.DeepEqual(&test.dst, &test.expected) { 395 | // t.Errorf("Expected «%» actual ") 396 | // } 397 | } 398 | } 399 | 400 | func TestCopier_Cyclic(t *testing.T) { 401 | type List1 struct { 402 | Value int 403 | Next *List1 404 | } 405 | type List2 struct { 406 | Value int 407 | Next *List2 408 | } 409 | 410 | src := List1{ 411 | Value: 1, 412 | Next: &List1{ 413 | Value: 2, 414 | Next: &List1{ 415 | Value: 3, 416 | }, 417 | }, 418 | } 419 | dst := List2{} 420 | 421 | New().Get(&dst, &src).Copy(&dst, &src) 422 | equal(t, dst, src) 423 | } 424 | 425 | func TestCopiers_Parallel(t *testing.T) { 426 | type Flags struct { 427 | State int 428 | } 429 | 430 | type List struct { 431 | Flags Flags 432 | Value int 433 | Next *List 434 | } 435 | 436 | var wg sync.WaitGroup 437 | for i := 0; i < 4; i++ { 438 | i := i 439 | wg.Add(1) 440 | go func() { 441 | for j := 0; j < 50; j++ { 442 | src := List{ 443 | Flags: Flags{State: i}, 444 | Value: j * 10, 445 | Next: &List{ 446 | Flags: Flags{State: i}, 447 | Value: j*10 + 1, 448 | Next: &List{ 449 | Flags: Flags{State: i}, 450 | Value: j*10 + 2, 451 | }, 452 | }, 453 | } 454 | dst := List{} 455 | 456 | Copy(&dst, &src) 457 | equal(t, dst, src) 458 | } 459 | wg.Done() 460 | }() 461 | } 462 | wg.Wait() 463 | } 464 | 465 | func TestCopier_StructCopiers(t *testing.T) { 466 | type testStruct1 struct { 467 | Value int 468 | } 469 | type testStruct2 struct { 470 | Value int 471 | } 472 | src := testStruct1{Value: 10} 473 | pSrc := &src 474 | copiers := New() 475 | 476 | dst := testStruct2{} 477 | pDst := &dst 478 | 479 | copiers.Get(&dst, &src).Copy(&dst, &src) 480 | 481 | dst = testStruct2{} 482 | copiers.Get(&pDst, &src).Copy(&pDst, &src) 483 | equal(t, dst, src) 484 | 485 | dst = testStruct2{} 486 | copiers.Get(&dst, &pSrc).Copy(&dst, &pSrc) 487 | equal(t, dst, src) 488 | 489 | dst = testStruct2{} 490 | copiers.Get(&pDst, &pSrc).Copy(&pDst, &pSrc) 491 | equal(t, dst, src) 492 | } 493 | 494 | func TestCopier_Slice(t *testing.T) { 495 | type internal1 struct { 496 | I int 497 | S string 498 | } 499 | 500 | type slice1 []internal1 501 | 502 | type internal2 struct { 503 | I int 504 | S string 505 | } 506 | 507 | type slice2 []internal2 508 | 509 | type testStruct1 struct { 510 | S1 slice1 511 | S2 *slice1 512 | S3 slice1 513 | S4 *slice1 514 | } 515 | 516 | type testStruct2 struct { 517 | S1 slice2 518 | S2 slice1 519 | S3 *slice1 520 | S4 *slice1 521 | } 522 | 523 | src := testStruct1{ 524 | S1: slice1{ 525 | internal1{I: 10, S: "10"}, 526 | internal1{I: 9, S: "9"}, 527 | internal1{I: 8, S: "8"}, 528 | internal1{I: 7, S: "7"}, 529 | internal1{I: 6, S: "6"}, 530 | internal1{I: 5, S: "5"}, 531 | internal1{I: 4, S: "4"}, 532 | internal1{I: 3, S: "3"}, 533 | internal1{I: 2, S: "2"}, 534 | internal1{I: 1, S: "1"}, 535 | }, 536 | S2: &slice1{ 537 | internal1{I: 10, S: "10"}, 538 | internal1{I: 9, S: "9"}, 539 | internal1{I: 8, S: "8"}, 540 | internal1{I: 7, S: "7"}, 541 | internal1{I: 6, S: "6"}, 542 | internal1{I: 5, S: "5"}, 543 | internal1{I: 4, S: "4"}, 544 | internal1{I: 3, S: "3"}, 545 | internal1{I: 2, S: "2"}, 546 | internal1{I: 1, S: "1"}, 547 | }, 548 | S3: slice1{ 549 | internal1{I: 10, S: "10"}, 550 | internal1{I: 9, S: "9"}, 551 | internal1{I: 8, S: "8"}, 552 | internal1{I: 7, S: "7"}, 553 | internal1{I: 6, S: "6"}, 554 | internal1{I: 5, S: "5"}, 555 | internal1{I: 4, S: "4"}, 556 | internal1{I: 3, S: "3"}, 557 | internal1{I: 2, S: "2"}, 558 | internal1{I: 1, S: "1"}, 559 | }, 560 | S4: &slice1{ 561 | internal1{I: 10, S: "10"}, 562 | internal1{I: 9, S: "9"}, 563 | internal1{I: 8, S: "8"}, 564 | internal1{I: 7, S: "7"}, 565 | internal1{I: 6, S: "6"}, 566 | internal1{I: 5, S: "5"}, 567 | internal1{I: 4, S: "4"}, 568 | internal1{I: 3, S: "3"}, 569 | internal1{I: 2, S: "2"}, 570 | internal1{I: 1, S: "1"}, 571 | }, 572 | } 573 | dst := testStruct2{} 574 | 575 | New().Get(&dst, &src).Copy(&dst, &src) 576 | equal(t, dst, src) 577 | } 578 | --------------------------------------------------------------------------------