├── CHANGELOG.md ├── LICENSE ├── README.md ├── benchmarks ├── complex_struct_benchmark_test.go ├── flat_structs_benchmark_test.go ├── go.mod ├── go.sum ├── map_benchmark_test.go ├── nested_struct_benckmark_test.go ├── simple_struct_5_benchmark_test.go ├── slice_benchmark_test.go ├── slice_of_structs_benchmark_test.go └── utils.go ├── clone.go ├── clone_test.go ├── go.mod └── go.sum /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # kamino 2 | 3 | ## v0.0.1 - 2023-04-24 4 | 5 | ### Added 6 | 7 | - Clone function 8 | - WithForceUnexported setting 9 | - WithZeroUnexported setting 10 | - WithErrOnUnsupported setting -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Egor Gartman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kamino - deep copy library 2 | 3 | Kamino is a programming library that provides faster way to [deep copy](https://developer.mozilla.org/en-US/docs/Glossary/Deep_copy) data structures in Golang. 4 | It designed to be fast and avoid unnecessary allocations. See [benchmarks](#benchmarks). Also It uses generics to avoid type assertions in client's code. 5 | Although kamino designed to be fast, it handle circular pointers. 6 | By default kamino makes a shallow copy for unsupported kinds of values (chans and funcs) and unexported struct fields. Buit in can be changed via functional options. 7 | 8 | ## Installation 9 | 10 | `go get github.com/LastPossum/kamino` 11 | 12 | ## Usage 13 | 14 | To use the library, you need to import it into your project: 15 | 16 | `import "github.com/LastPossum/kamino"` 17 | 18 | Once you have imported the library, you can use the `func Clone[T any](src T, opts ...funcOptions) (T, error)` function to create a deep copy of your data structure. 19 | 20 | ### Sample Program 21 | 22 | ```go 23 | package main 24 | 25 | import ( 26 | "fmt" 27 | "reflect" 28 | 29 | "github.com/LastPossum/kamino" 30 | ) 31 | 32 | type Foo struct { 33 | A string 34 | B int 35 | C *float64 36 | D any 37 | } 38 | 39 | func main() { 40 | var f float64 = 1 41 | original := Foo{ 42 | A: "a string", 43 | B: 1114, 44 | C: &f, 45 | } 46 | 47 | original.D = &original 48 | 49 | copy, err := kamino.Clone(original) 50 | if err != nil { 51 | fmt.Println(err) 52 | return 53 | } 54 | 55 | fmt.Println(original, original.D) 56 | fmt.Println(copy, copy.D) 57 | fmt.Println(reflect.DeepEqual(original, copy)) 58 | } 59 | ``` 60 | 61 | ### Sample Output 62 | 63 | _Note that the values are all the same, but the addresses are all different. Note also that circular references are handled._ 64 | 65 | ```go 66 | {a string 1114 0xc00001c030 0xc0000161b0} &{a string 1114 0xc00001c030 0xc0000161b0} 67 | {a string 1114 0xc00001c038 0xc0000162a0} &{a string 1114 0xc00001c038 0xc0000162a0} 68 | true 69 | ``` 70 | 71 | ## Options 72 | 73 | Kamino supports several following options: 74 | - `WithExpectedPtrsCount` - Use it to set the initial capacity for the pointers map used during the cloning process. If the amount of pointers in the source object is known and rather big, this setting could reduce allocations and slightly improve performance. 75 | - `WithForceUnexported` - when this setting is enabled, unexported fields will be cloned forcefully. 76 | - `WithZeroUnexported` - when this setting is enabled, unexported fields will be forcefully set to zero value of their kind. 77 | - `WithErrOnUnsupported` - when this setting is enabled, attempting to clone channels and functions, even if they are nested, will result in an error. 78 | 79 | ## Benchmarks 80 | 81 | Results as of April 25, 2023 with Go go1.20.3 on OSx Intel Core i5 (4 core 2,4 GHz) 82 | 83 | ``` 84 | BenchmarkCloneKaminoComplexStruct-8 277168 3943 ns/op 2013 B/op 27 allocs/op 85 | BenchmarkNestedStructKamino/kamino_for_7_fiels_nested_struct-8 4228910 281.3 ns/op 56 B/op 4 allocs/op 86 | BenchmarkCloneMap-8 331911 3482 ns/op 1424 B/op 38 allocs/op 87 | 88 | ``` 89 | ## Limitation 90 | 91 | For now, Kamino does not support circular slices and maps. Additionally, if two slices in the source object point to the same underlying array, this feature will not be kept in the copy object. Therefore, these cases will cause a stack overflow: 92 | 93 | ``` 94 | func TestCircularSlice(t *testing.T) { 95 | a := []any{nil} 96 | a[0] = a 97 | cp, err := kamino.Clone(a) 98 | 99 | assert.NoError(t, err) 100 | assert.Equal(t, cp, a) 101 | } 102 | 103 | func TestCircularMap(t *testing.T) { 104 | a := map[string]any{"a": nil} 105 | a["a"] = a 106 | cp, err := kamino.Clone(a) 107 | 108 | assert.NoError(t, err) 109 | assert.Equal(t, cp, a) 110 | } 111 | 112 | ``` 113 | 114 | ## License 115 | 116 | This library is released under the MIT License. You are free to use, modify, and distribute it as you see fit. See the LICENSE file for more information. -------------------------------------------------------------------------------- /benchmarks/complex_struct_benchmark_test.go: -------------------------------------------------------------------------------- 1 | package kamino_benchmark 2 | 3 | import ( 4 | "testing" 5 | 6 | barkimedes "github.com/barkimedes/go-deepcopy" 7 | mohae "github.com/mohae/deepcopy" 8 | 9 | "github.com/LastPossum/kamino" 10 | ) 11 | 12 | type barer interface { 13 | Bar() int 14 | } 15 | 16 | type simpleStructBenchmark struct { 17 | Int int 18 | Float64 float64 19 | String string 20 | } 21 | 22 | func (s *simpleStructBenchmark) Bar() int { 23 | return s.Int 24 | } 25 | 26 | type complexStructForBench struct { 27 | Int int 28 | Float64 float64 29 | String string 30 | 31 | Struct simpleStructBenchmark 32 | Interface barer 33 | 34 | PointerToInt *int 35 | PointerToFloat64 *float64 36 | PointerToString *string 37 | PointerToStruct *simpleStructBenchmark 38 | 39 | ArrayOfInt [10]int 40 | ArrayOfSimpleStruct [5]simpleStructBenchmark 41 | 42 | SliceOfInt []int 43 | SliceOfSimpleStruct []simpleStructBenchmark 44 | SliceOfPtrsToInt []*int 45 | SliceOfStructPtrs []*simpleStructBenchmark 46 | } 47 | 48 | func simpleStructForBenchCp() simpleStructBenchmark { 49 | return simpleStructForBenchInstance 50 | } 51 | 52 | var ( 53 | simpleStructForBenchInstance = simpleStructBenchmark{ 54 | Int: 1, 55 | Float64: 2, 56 | String: "3", 57 | } 58 | complexStructForBenchInstance = complexStructForBench{ 59 | Int: 1, 60 | Float64: 2, 61 | String: "3", 62 | Struct: simpleStructForBenchInstance, 63 | Interface: ptrTo(simpleStructForBenchCp()), 64 | PointerToInt: ptrTo(4), 65 | PointerToFloat64: ptrTo(5.0), 66 | PointerToString: ptrTo("6"), 67 | PointerToStruct: ptrTo(simpleStructForBenchCp()), 68 | ArrayOfInt: [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, 69 | ArrayOfSimpleStruct: [...]simpleStructBenchmark{simpleStructForBenchInstance, simpleStructForBenchInstance, simpleStructForBenchInstance, simpleStructForBenchInstance, simpleStructForBenchInstance}, 70 | SliceOfInt: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, 71 | SliceOfSimpleStruct: []simpleStructBenchmark{simpleStructForBenchInstance, simpleStructForBenchInstance, simpleStructForBenchInstance, simpleStructForBenchInstance, simpleStructForBenchInstance}, 72 | SliceOfPtrsToInt: []*int{ptrTo(1), ptrTo(2), ptrTo(3), ptrTo(4), ptrTo(5)}, 73 | SliceOfStructPtrs: []*simpleStructBenchmark{ptrTo(simpleStructForBenchCp()), ptrTo(simpleStructForBenchCp()), ptrTo(simpleStructForBenchCp())}, 74 | } 75 | ) 76 | 77 | func BenchmarkCloneBarkimedesComplexStruct(b *testing.B) { 78 | for i := 0; i < b.N; i++ { 79 | barkimedes.Anything(complexStructForBenchInstance) 80 | } 81 | } 82 | 83 | func BenchmarkCloneMohaeComplexStruct(b *testing.B) { 84 | for i := 0; i < b.N; i++ { 85 | mohae.Copy(complexStructForBenchInstance) 86 | } 87 | } 88 | 89 | func BenchmarkCloneJsonComplexStruct(b *testing.B) { 90 | for i := 0; i < b.N; i++ { 91 | cloneJSON(complexStructForBenchInstance) 92 | } 93 | } 94 | 95 | func BenchmarkCloneKaminoComplexStruct(b *testing.B) { 96 | for i := 0; i < b.N; i++ { 97 | kamino.Clone(complexStructForBenchInstance) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /benchmarks/flat_structs_benchmark_test.go: -------------------------------------------------------------------------------- 1 | package kamino_benchmark 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/LastPossum/kamino" 9 | 10 | barkimedes "github.com/barkimedes/go-deepcopy" 11 | mohae "github.com/mohae/deepcopy" 12 | ) 13 | 14 | type benchSimpleStruct5 struct { 15 | I1 int 16 | F1 float64 17 | S1 string 18 | B1 byte 19 | L1 bool 20 | } 21 | 22 | type benchSimpleStruct10 struct { 23 | I1 int 24 | F1 float64 25 | S1 string 26 | B1 byte 27 | L1 bool 28 | I2 int 29 | F2 float64 30 | S2 string 31 | B2 byte 32 | L2 bool 33 | } 34 | 35 | type benchSimpleStruct15 struct { 36 | I1 int 37 | F1 float64 38 | S1 string 39 | B1 byte 40 | L1 bool 41 | I2 int 42 | F2 float64 43 | S2 string 44 | B2 byte 45 | L2 bool 46 | I3 int 47 | F3 float64 48 | S3 string 49 | B3 byte 50 | L3 bool 51 | } 52 | 53 | type benchSimpleStruct20 struct { 54 | I1 int 55 | F1 float64 56 | S1 string 57 | B1 byte 58 | L1 bool 59 | I2 int 60 | F2 float64 61 | S2 string 62 | B2 byte 63 | L2 bool 64 | I3 int 65 | F3 float64 66 | S3 string 67 | B3 byte 68 | L3 bool 69 | I4 int 70 | F4 float64 71 | S4 string 72 | B4 byte 73 | L4 bool 74 | } 75 | type benchSimpleStruct25 struct { 76 | I1 int 77 | F1 float64 78 | S1 string 79 | B1 byte 80 | L1 bool 81 | I2 int 82 | F2 float64 83 | S2 string 84 | B2 byte 85 | L2 bool 86 | I3 int 87 | F3 float64 88 | S3 string 89 | B3 byte 90 | L3 bool 91 | I4 int 92 | F4 float64 93 | S4 string 94 | B4 byte 95 | L4 bool 96 | I5 int 97 | F5 float64 98 | S5 string 99 | B5 byte 100 | L5 bool 101 | } 102 | type benchSimpleStruct30 struct { 103 | I1 int 104 | F1 float64 105 | S1 string 106 | B1 byte 107 | L1 bool 108 | I2 int 109 | F2 float64 110 | S2 string 111 | B2 byte 112 | L2 bool 113 | I3 int 114 | F3 float64 115 | S3 string 116 | B3 byte 117 | L3 bool 118 | I4 int 119 | F4 float64 120 | S4 string 121 | B4 byte 122 | L4 bool 123 | I5 int 124 | F5 float64 125 | S5 string 126 | B5 byte 127 | L5 bool 128 | I6 int 129 | F6 float64 130 | S6 string 131 | B6 byte 132 | L6 bool 133 | } 134 | type benchSimpleStruct35 struct { 135 | I1 int 136 | F1 float64 137 | S1 string 138 | B1 byte 139 | L1 bool 140 | I2 int 141 | F2 float64 142 | S2 string 143 | B2 byte 144 | L2 bool 145 | I3 int 146 | F3 float64 147 | S3 string 148 | B3 byte 149 | L3 bool 150 | I4 int 151 | F4 float64 152 | S4 string 153 | B4 byte 154 | L4 bool 155 | I5 int 156 | F5 float64 157 | S5 string 158 | B5 byte 159 | L5 bool 160 | I6 int 161 | F6 float64 162 | S6 string 163 | B6 byte 164 | L6 bool 165 | I7 int 166 | F7 float64 167 | S7 string 168 | B7 byte 169 | L7 bool 170 | } 171 | 172 | type benchSimpleStruct40 struct { 173 | I1 int 174 | F1 float64 175 | S1 string 176 | B1 byte 177 | L1 bool 178 | I2 int 179 | F2 float64 180 | S2 string 181 | B2 byte 182 | L2 bool 183 | I3 int 184 | F3 float64 185 | S3 string 186 | B3 byte 187 | L3 bool 188 | I4 int 189 | F4 float64 190 | S4 string 191 | B4 byte 192 | L4 bool 193 | I5 int 194 | F5 float64 195 | S5 string 196 | B5 byte 197 | L5 bool 198 | I6 int 199 | F6 float64 200 | S6 string 201 | B6 byte 202 | L6 bool 203 | I7 int 204 | F7 float64 205 | S7 string 206 | B7 byte 207 | L7 bool 208 | I8 int 209 | F8 float64 210 | S8 string 211 | B8 byte 212 | L8 bool 213 | } 214 | 215 | type benchSimpleStruct45 struct { 216 | I1 int 217 | F1 float64 218 | S1 string 219 | B1 byte 220 | L1 bool 221 | I2 int 222 | F2 float64 223 | S2 string 224 | B2 byte 225 | L2 bool 226 | I3 int 227 | F3 float64 228 | S3 string 229 | B3 byte 230 | L3 bool 231 | I4 int 232 | F4 float64 233 | S4 string 234 | B4 byte 235 | L4 bool 236 | I5 int 237 | F5 float64 238 | S5 string 239 | B5 byte 240 | L5 bool 241 | I6 int 242 | F6 float64 243 | S6 string 244 | B6 byte 245 | L6 bool 246 | I7 int 247 | F7 float64 248 | S7 string 249 | B7 byte 250 | L7 bool 251 | I8 int 252 | F8 float64 253 | S8 string 254 | B8 byte 255 | L8 bool 256 | I9 int 257 | F9 float64 258 | S9 string 259 | B9 byte 260 | L9 bool 261 | } 262 | 263 | type benchSimpleStruct50 struct { 264 | I1 int 265 | F1 float64 266 | S1 string 267 | B1 byte 268 | L1 bool 269 | I2 int 270 | F2 float64 271 | S2 string 272 | B2 byte 273 | L2 bool 274 | I3 int 275 | F3 float64 276 | S3 string 277 | B3 byte 278 | L3 bool 279 | I4 int 280 | F4 float64 281 | S4 string 282 | B4 byte 283 | L4 bool 284 | I5 int 285 | F5 float64 286 | S5 string 287 | B5 byte 288 | L5 bool 289 | I6 int 290 | F6 float64 291 | S6 string 292 | B6 byte 293 | L6 bool 294 | I7 int 295 | F7 float64 296 | S7 string 297 | B7 byte 298 | L7 bool 299 | I8 int 300 | F8 float64 301 | S8 string 302 | B8 byte 303 | L8 bool 304 | I9 int 305 | F9 float64 306 | S9 string 307 | B9 byte 308 | L9 bool 309 | I10 int 310 | F10 float64 311 | S10 string 312 | B10 byte 313 | L10 bool 314 | } 315 | 316 | var flats = map[int]any{ 317 | 5: benchSimpleStruct5{1, 2, "3", 4, true}, 318 | 10: benchSimpleStruct10{1, 2, "3", 4, true, 1, 2, "3", 4, true}, 319 | 15: benchSimpleStruct15{1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true}, 320 | 20: benchSimpleStruct20{1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true}, 321 | 25: benchSimpleStruct25{1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true}, 322 | 30: benchSimpleStruct30{1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true}, 323 | 35: benchSimpleStruct35{1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true}, 324 | 40: benchSimpleStruct40{1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true}, 325 | 45: benchSimpleStruct45{1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true}, 326 | 50: benchSimpleStruct50{1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true, 1, 2, "3", 4, true}, 327 | } 328 | 329 | var structFieldsKeys = []int{5, 10, 15, 20, 25, 30, 35, 40, 45, 50} 330 | 331 | func BenchmarkFlatStruct(b *testing.B) { 332 | for _, k := range structFieldsKeys { 333 | st := flats[k] 334 | b.Run(fmt.Sprintf("barkimedes for %d fiels flat struct", k), func(b *testing.B) { 335 | for i := 0; i < b.N; i++ { 336 | barkimedes.Anything(st) 337 | } 338 | }) 339 | } 340 | 341 | for _, k := range structFieldsKeys { 342 | st := flats[k] 343 | b.Run(fmt.Sprintf("mohae for %d fiels flat struct", k), func(b *testing.B) { 344 | for i := 0; i < b.N; i++ { 345 | mohae.Copy(st) 346 | } 347 | }) 348 | } 349 | 350 | for _, k := range structFieldsKeys { 351 | st := flats[k] 352 | b.Run(fmt.Sprintf("json for %d fiels flat struct", k), func(b *testing.B) { 353 | for i := 0; i < b.N; i++ { 354 | cloneJSON(st) 355 | } 356 | }) 357 | } 358 | 359 | for _, k := range structFieldsKeys { 360 | st := flats[k] 361 | b.Run(fmt.Sprintf("msgpack for %d fiels flat struct", k), func(b *testing.B) { 362 | for i := 0; i < b.N; i++ { 363 | cloneMsgPack(st) 364 | } 365 | }) 366 | } 367 | 368 | for _, k := range structFieldsKeys { 369 | st := flats[k] 370 | b.Run(fmt.Sprintf("kamino for %d fiels flat struct", k), func(b *testing.B) { 371 | for i := 0; i < b.N; i++ { 372 | kamino.Clone(st) 373 | } 374 | }) 375 | } 376 | } 377 | 378 | func BenchmarkFlatStructKamino(b *testing.B) { 379 | for _, k := range structFieldsKeys { 380 | st := flats[k] 381 | b.Run(fmt.Sprintf("kamino for %d fiels flat struct", k), func(b *testing.B) { 382 | for i := 0; i < b.N; i++ { 383 | kamino.Clone(st) 384 | } 385 | }) 386 | } 387 | } 388 | 389 | func cpInit(x any) any { 390 | original := reflect.ValueOf(x) 391 | 392 | cpy := reflect.New(original.Type()).Elem() 393 | cp(original, cpy) 394 | 395 | return cpy.Interface() 396 | } 397 | 398 | func isBoxedNil(src any) bool { 399 | return src == nil || src == any(nil) 400 | } 401 | 402 | func cpInitT[T any](src T) (T, error) { 403 | if isBoxedNil(src) { 404 | return src, nil 405 | } 406 | 407 | valPtr := reflect.ValueOf(&src) 408 | err := cloneNested(valPtr) 409 | return src, err 410 | } 411 | 412 | func cp(original, cpy reflect.Value) { 413 | if original.Kind() != reflect.Struct { 414 | cpy.Set(original) 415 | return 416 | } 417 | for i := 0; i < original.NumField(); i++ { 418 | if !cpy.CanSet() { 419 | continue 420 | } 421 | 422 | cp(original.Field(i), cpy.Field(i)) 423 | } 424 | } 425 | 426 | func cloneNested(valPtr reflect.Value) error { 427 | v := valPtr.Elem() 428 | 429 | switch v.Kind() { 430 | case reflect.Struct: 431 | for i := 0; i < v.NumField(); i++ { 432 | 433 | wField := v.Field(i) 434 | 435 | if wField.CanSet() { 436 | if err := cloneNested(wField.Addr()); err != nil { 437 | return err 438 | } 439 | continue 440 | } 441 | } 442 | } 443 | 444 | return nil 445 | } 446 | 447 | func BenchmarkX(b *testing.B) { 448 | for i := 0; i < b.N; i++ { 449 | cpInit(nestedSingle) 450 | } 451 | } 452 | 453 | func BenchmarkY(b *testing.B) { 454 | for i := 0; i < b.N; i++ { 455 | cpInitT(nestedSingle) 456 | } 457 | } 458 | -------------------------------------------------------------------------------- /benchmarks/go.mod: -------------------------------------------------------------------------------- 1 | module testing.fun/kamino/benchmarks 2 | 3 | go 1.21 4 | 5 | toolchain go1.23.0 6 | 7 | require ( 8 | github.com/LastPossum/kamino v0.0.1 9 | github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df 10 | github.com/jinzhu/copier v0.4.0 11 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 12 | github.com/vmihailenco/msgpack v4.0.4+incompatible 13 | ) 14 | 15 | require ( 16 | github.com/golang/protobuf v1.5.4 // indirect 17 | github.com/kr/pretty v0.3.1 // indirect 18 | google.golang.org/appengine v1.6.8 // indirect 19 | google.golang.org/protobuf v1.35.2 // indirect 20 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 21 | ) 22 | 23 | replace github.com/LastPossum/kamino => ../ 24 | -------------------------------------------------------------------------------- /benchmarks/go.sum: -------------------------------------------------------------------------------- 1 | github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df h1:GSoSVRLoBaFpOOds6QyY1L8AX7uoY+Ln3BHc22W40X0= 2 | github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df/go.mod h1:hiVxq5OP2bUGBRNS3Z/bt/reCLFNbdcST6gISi1fiOM= 3 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 7 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 8 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 9 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 10 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 11 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 12 | github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= 13 | github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= 14 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 15 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 16 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 17 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 18 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 19 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 20 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 21 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= 22 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= 23 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 24 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 25 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 26 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 27 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 28 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 29 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 30 | github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= 31 | github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= 32 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 33 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 34 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 35 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 36 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 37 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 38 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 39 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 40 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 41 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 42 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 43 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 44 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 45 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 46 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 47 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 48 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 49 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 50 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 51 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 52 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 53 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 54 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 55 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 56 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 57 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 58 | google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= 59 | google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= 60 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 61 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 62 | google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= 63 | google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 64 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 65 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 66 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 67 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 68 | -------------------------------------------------------------------------------- /benchmarks/map_benchmark_test.go: -------------------------------------------------------------------------------- 1 | package kamino_benchmark 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/LastPossum/kamino" 7 | barkimedes "github.com/barkimedes/go-deepcopy" 8 | mohae "github.com/mohae/deepcopy" 9 | ) 10 | 11 | var mp = map[string]any{ 12 | "int": 1, 13 | "float": 2., 14 | "string": "3", 15 | "bytes": make([]byte, 128), 16 | "structs": []*simpleStruct{ 17 | {1, 2., "3"}, 18 | {1, 2., "3"}, 19 | {1, 2., "3"}, 20 | }, 21 | "nested": map[string]any{ 22 | "int": 1, 23 | "float": 2., 24 | }, 25 | } 26 | 27 | func BenchmarkCloneBarkimedesMap(b *testing.B) { 28 | for i := 0; i < b.N; i++ { 29 | barkimedes.Anything(mp) 30 | } 31 | } 32 | 33 | func BenchmarkCloneMohaeMap(b *testing.B) { 34 | for i := 0; i < b.N; i++ { 35 | mohae.Copy(mp) 36 | } 37 | } 38 | 39 | func BenchmarkCloneJsonMap(b *testing.B) { 40 | for i := 0; i < b.N; i++ { 41 | cloneJSON(mp) 42 | } 43 | } 44 | 45 | func BenchmarkCloneMsgPacknMap(b *testing.B) { 46 | for i := 0; i < b.N; i++ { 47 | cloneMsgPack(mp) 48 | } 49 | } 50 | 51 | func BenchmarkCloneMap(b *testing.B) { 52 | for i := 0; i < b.N; i++ { 53 | kamino.Clone(mp) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /benchmarks/nested_struct_benckmark_test.go: -------------------------------------------------------------------------------- 1 | package kamino_benchmark 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/LastPossum/kamino" 8 | barkimedes "github.com/barkimedes/go-deepcopy" 9 | mohae "github.com/mohae/deepcopy" 10 | ) 11 | 12 | type NestedLevel1 struct { 13 | Payload int 14 | } 15 | 16 | type NestedLevel2 struct { 17 | Payload NestedLevel1 18 | } 19 | type NestedLevel3 struct { 20 | Payload NestedLevel2 21 | } 22 | type NestedLevel4 struct { 23 | Payload NestedLevel3 24 | } 25 | 26 | type NestedLevel5 struct { 27 | Payload NestedLevel4 28 | } 29 | 30 | type NestedLevel6 struct { 31 | Payload NestedLevel5 32 | } 33 | 34 | type NestedLevel7 struct { 35 | Payload NestedLevel6 36 | } 37 | 38 | type NestedLevel8 struct { 39 | Payload NestedLevel7 40 | } 41 | 42 | type NestedLevel9 struct { 43 | Payload NestedLevel8 44 | } 45 | 46 | type NestedLevel10 struct { 47 | Payload NestedLevel9 48 | } 49 | 50 | var ( 51 | i = 1 52 | nestedMap = map[int]any{ 53 | 1: NestedLevel1{i}, 54 | 2: NestedLevel2{NestedLevel1{i}}, 55 | 3: NestedLevel3{NestedLevel2{NestedLevel1{i}}}, 56 | 4: NestedLevel4{NestedLevel3{NestedLevel2{NestedLevel1{i}}}}, 57 | 5: NestedLevel5{NestedLevel4{NestedLevel3{NestedLevel2{NestedLevel1{i}}}}}, 58 | 6: NestedLevel6{NestedLevel5{NestedLevel4{NestedLevel3{NestedLevel2{NestedLevel1{i}}}}}}, 59 | 7: NestedLevel7{NestedLevel6{NestedLevel5{NestedLevel4{NestedLevel3{NestedLevel2{NestedLevel1{i}}}}}}}, 60 | 8: NestedLevel8{NestedLevel7{NestedLevel6{NestedLevel5{NestedLevel4{NestedLevel3{NestedLevel2{NestedLevel1{i}}}}}}}}, 61 | 9: NestedLevel9{NestedLevel8{NestedLevel7{NestedLevel6{NestedLevel5{NestedLevel4{NestedLevel3{NestedLevel2{NestedLevel1{i}}}}}}}}}, 62 | 10: NestedLevel10{NestedLevel9{NestedLevel8{NestedLevel7{NestedLevel6{NestedLevel5{NestedLevel4{NestedLevel3{NestedLevel2{NestedLevel1{i}}}}}}}}}}, 63 | } 64 | 65 | nestedSingle = NestedLevel8{NestedLevel7{NestedLevel6{NestedLevel5{NestedLevel4{NestedLevel3{NestedLevel2{NestedLevel1{i}}}}}}}} 66 | ) 67 | 68 | func BenchmarkNestedKamino(b *testing.B) { 69 | for i := 0; i < b.N; i++ { 70 | kamino.Clone(nestedSingle) 71 | } 72 | } 73 | 74 | func BenchmarkNestedMohae(b *testing.B) { 75 | for i := 0; i < b.N; i++ { 76 | mohae.Copy(nestedSingle) 77 | } 78 | } 79 | 80 | func BenchmarkNestedStruct(b *testing.B) { 81 | for k := 1; k <= 10; k++ { 82 | st := nestedMap[k] 83 | b.Run(fmt.Sprintf("barkimedes for %d fiels nested struct", k), func(b *testing.B) { 84 | for i := 0; i < b.N; i++ { 85 | barkimedes.Anything(st) 86 | } 87 | }) 88 | } 89 | for k := 1; k <= 10; k++ { 90 | st := nestedMap[k] 91 | b.Run(fmt.Sprintf("mohae for %d fiels nested struct", k), func(b *testing.B) { 92 | for i := 0; i < b.N; i++ { 93 | mohae.Copy(st) 94 | } 95 | }) 96 | } 97 | for k := 1; k <= 10; k++ { 98 | st := nestedMap[k] 99 | b.Run(fmt.Sprintf("json for %d fiels nested struct", k), func(b *testing.B) { 100 | for i := 0; i < b.N; i++ { 101 | cloneJSON(st) 102 | } 103 | }) 104 | } 105 | 106 | for k := 1; k <= 10; k++ { 107 | st := nestedMap[k] 108 | b.Run(fmt.Sprintf("msgpack for %d fiels nested struct", k), func(b *testing.B) { 109 | for i := 0; i < b.N; i++ { 110 | cloneMsgPack(st) 111 | } 112 | }) 113 | } 114 | 115 | for k := 1; k <= 10; k++ { 116 | st := nestedMap[k] 117 | b.Run(fmt.Sprintf("kamino for %d fiels nested struct", k), func(b *testing.B) { 118 | for i := 0; i < b.N; i++ { 119 | kamino.Clone(st) 120 | } 121 | }) 122 | } 123 | } 124 | 125 | func BenchmarkNestedStructKamino(b *testing.B) { 126 | for k := 1; k <= 10; k++ { 127 | st := nestedMap[k] 128 | b.Run(fmt.Sprintf("kamino for %d fiels nested struct", k), func(b *testing.B) { 129 | for i := 0; i < b.N; i++ { 130 | kamino.Clone(st) 131 | } 132 | }) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /benchmarks/simple_struct_5_benchmark_test.go: -------------------------------------------------------------------------------- 1 | package kamino_benchmark 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/LastPossum/kamino" 7 | barkimedes "github.com/barkimedes/go-deepcopy" 8 | jinzhu "github.com/jinzhu/copier" 9 | mohae "github.com/mohae/deepcopy" 10 | ) 11 | 12 | var benchSimpleStruct5Instance = benchSimpleStruct5{1, 2, "3", 4, true} 13 | 14 | //go:noinline 15 | func plainCopySimpleStruct5(src benchSimpleStruct5) benchSimpleStruct5 { 16 | return src 17 | } 18 | 19 | func BenchmarkPlainCopySimpleStruct5(b *testing.B) { 20 | for i := 0; i < b.N; i++ { 21 | plainCopySimpleStruct5(benchSimpleStruct5Instance) 22 | } 23 | } 24 | 25 | func BenchmarkCloneBarkimedesSimpleStruct5(b *testing.B) { 26 | for i := 0; i < b.N; i++ { 27 | barkimedes.Anything(benchSimpleStruct5Instance) 28 | } 29 | } 30 | 31 | func BenchmarkCloneMohaeSimpleStruct5(b *testing.B) { 32 | for i := 0; i < b.N; i++ { 33 | mohae.Copy(benchSimpleStruct5Instance) 34 | } 35 | } 36 | 37 | func BenchmarkCloneJinzhuSimpleStruct5(b *testing.B) { 38 | for i := 0; i < b.N; i++ { 39 | var res benchSimpleStruct5 40 | jinzhu.CopyWithOption(&res, &benchSimpleStruct5Instance, jinzhu.Option{IgnoreEmpty: true, DeepCopy: true}) 41 | } 42 | } 43 | 44 | func BenchmarkCloneJsonSimpleStruct5(b *testing.B) { 45 | for i := 0; i < b.N; i++ { 46 | cloneJSON(benchSimpleStruct5Instance) 47 | } 48 | } 49 | 50 | func BenchmarkCloneMsgPackSimpleStruct5(b *testing.B) { 51 | for i := 0; i < b.N; i++ { 52 | cloneMsgPack(benchSimpleStruct5Instance) 53 | } 54 | } 55 | 56 | func BenchmarkCloneSimpleStruct(b *testing.B) { 57 | for i := 0; i < b.N; i++ { 58 | kamino.Clone(benchSimpleStruct5Instance) 59 | } 60 | } 61 | 62 | type singleStruct struct { 63 | i int64 64 | // i2 int64 65 | } 66 | 67 | func BenchmarkClone1(b *testing.B) { 68 | for i := 0; i < b.N; i++ { 69 | mohae.Copy(singleStruct{1}) 70 | } 71 | } 72 | 73 | func BenchmarkClone1_(b *testing.B) { 74 | for i := 0; i < b.N; i++ { 75 | kamino.Clone(singleStruct{1}) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /benchmarks/slice_benchmark_test.go: -------------------------------------------------------------------------------- 1 | package kamino_benchmark 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | barkimedes "github.com/barkimedes/go-deepcopy" 8 | mohae "github.com/mohae/deepcopy" 9 | 10 | "github.com/LastPossum/kamino" 11 | ) 12 | 13 | func mkByteSlice(l int) []byte { 14 | res := make([]byte, l) 15 | res[0] = 'b' 16 | return res 17 | } 18 | 19 | func BenchmarkBytesSlice(b *testing.B) { 20 | for i := 5; i < 10; i++ { 21 | k := 1 << i 22 | bytes := mkByteSlice(k) 23 | 24 | b.Run(fmt.Sprintf("barkimedes for %d bytes slice", k), func(b *testing.B) { 25 | for i := 0; i < b.N; i++ { 26 | barkimedes.Anything(bytes) 27 | } 28 | }) 29 | } 30 | 31 | for i := 5; i < 10; i++ { 32 | k := 1 << i 33 | bytes := mkByteSlice(k) 34 | 35 | b.Run(fmt.Sprintf("mohae for %d bytes slice", k), func(b *testing.B) { 36 | for i := 0; i < b.N; i++ { 37 | mohae.Copy(bytes) 38 | } 39 | }) 40 | } 41 | 42 | for i := 5; i < 10; i++ { 43 | k := 1 << i 44 | bytes := mkByteSlice(k) 45 | 46 | b.Run(fmt.Sprintf("json for %d bytes slice", k), func(b *testing.B) { 47 | for i := 0; i < b.N; i++ { 48 | cloneJSON(bytes) 49 | } 50 | }) 51 | } 52 | 53 | for i := 5; i < 10; i++ { 54 | k := 1 << i 55 | bytes := mkByteSlice(k) 56 | 57 | b.Run(fmt.Sprintf("msgpack for %d bytes slice", k), func(b *testing.B) { 58 | for i := 0; i < b.N; i++ { 59 | cloneMsgPack(bytes) 60 | } 61 | }) 62 | } 63 | 64 | for i := 5; i < 10; i++ { 65 | k := 1 << i 66 | bytes := mkByteSlice(k) 67 | 68 | b.Run(fmt.Sprintf("kamino for %d bytes slice", k), func(b *testing.B) { 69 | for i := 0; i < b.N; i++ { 70 | kamino.Clone(bytes) 71 | } 72 | }) 73 | } 74 | } 75 | 76 | func BenchmarkBytesSliceKamino(b *testing.B) { 77 | for i := 5; i < 10; i++ { 78 | k := 1 << i 79 | bytes := mkByteSlice(k) 80 | 81 | b.Run(fmt.Sprintf("kamino for %d bytes slice", k), func(b *testing.B) { 82 | for i := 0; i < b.N; i++ { 83 | kamino.Clone(bytes) 84 | } 85 | }) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /benchmarks/slice_of_structs_benchmark_test.go: -------------------------------------------------------------------------------- 1 | package kamino_benchmark 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "testing" 7 | 8 | barkimedes "github.com/barkimedes/go-deepcopy" 9 | mohae "github.com/mohae/deepcopy" 10 | 11 | "github.com/LastPossum/kamino" 12 | ) 13 | 14 | type simpleStruct struct { 15 | Int int 16 | Float64 float64 17 | String string 18 | } 19 | 20 | func genSliceOfStruct(lenStructSlice int) []simpleStruct { 21 | bigStructSlice := make([]simpleStruct, lenStructSlice) 22 | for i := 0; i < lenStructSlice; i++ { 23 | bigStructSlice[i].Int = i 24 | bigStructSlice[i].Float64 = float64(i) 25 | bigStructSlice[i].String = strconv.Itoa(i) 26 | } 27 | return bigStructSlice 28 | } 29 | 30 | func BenchmarkStructsSlice(b *testing.B) { 31 | for i := 5; i < 10; i++ { 32 | k := 1 << i 33 | structs := genSliceOfStruct(k) 34 | 35 | b.Run(fmt.Sprintf("barkimedes for %d structs slice", k), func(b *testing.B) { 36 | for i := 0; i < b.N; i++ { 37 | barkimedes.Anything(structs) 38 | } 39 | }) 40 | } 41 | for i := 5; i < 10; i++ { 42 | k := 1 << i 43 | structs := genSliceOfStruct(k) 44 | 45 | b.Run(fmt.Sprintf("mohae for %d structs slice", k), func(b *testing.B) { 46 | for i := 0; i < b.N; i++ { 47 | mohae.Copy(structs) 48 | } 49 | }) 50 | } 51 | for i := 5; i < 10; i++ { 52 | k := 1 << i 53 | structs := genSliceOfStruct(k) 54 | b.Run(fmt.Sprintf("json for %d structs slice", k), func(b *testing.B) { 55 | for i := 0; i < b.N; i++ { 56 | cloneJSON(structs) 57 | } 58 | }) 59 | } 60 | 61 | for i := 5; i < 10; i++ { 62 | k := 1 << i 63 | structs := genSliceOfStruct(k) 64 | b.Run(fmt.Sprintf("msgpack for %d structs slice", k), func(b *testing.B) { 65 | for i := 0; i < b.N; i++ { 66 | cloneMsgPack(structs) 67 | } 68 | }) 69 | } 70 | 71 | for i := 5; i < 10; i++ { 72 | k := 1 << i 73 | structs := genSliceOfStruct(k) 74 | b.Run(fmt.Sprintf("kamino for %d structs slice", k), func(b *testing.B) { 75 | for i := 0; i < b.N; i++ { 76 | kamino.Clone(structs) 77 | } 78 | }) 79 | } 80 | } 81 | 82 | func BenchmarkStructsSliceKamino(b *testing.B) { 83 | for i := 5; i < 10; i++ { 84 | k := 1 << i 85 | structs := genSliceOfStruct(k) 86 | 87 | b.Run(fmt.Sprintf("kamino for %d structs slice", k), func(b *testing.B) { 88 | for i := 0; i < b.N; i++ { 89 | kamino.Clone(structs) 90 | } 91 | }) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /benchmarks/utils.go: -------------------------------------------------------------------------------- 1 | package kamino_benchmark 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/vmihailenco/msgpack" 7 | ) 8 | 9 | func ptrTo[T any](v T) *T { 10 | return &v 11 | } 12 | 13 | func cloneJSON[T any](obj T) T { 14 | x, _ := json.Marshal(obj) 15 | var res T 16 | json.Unmarshal(x, &res) 17 | return res 18 | } 19 | 20 | func cloneMsgPack[T any](x T) T { 21 | b, _ := msgpack.Marshal(x) 22 | var res T 23 | msgpack.Unmarshal(b, &res) 24 | return res 25 | } 26 | -------------------------------------------------------------------------------- /clone.go: -------------------------------------------------------------------------------- 1 | package kamino 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "unsafe" 7 | ) 8 | 9 | type config struct { 10 | expectedPointersCount int 11 | unexportedStrategy unexportedStrategy 12 | errOnUnsuported bool 13 | } 14 | 15 | type funcOptions func(*config) 16 | 17 | // WithExpectedPtrsCount is a functional option for the Clone function that sets the initial capacity for the pointers map used during the cloning process. 18 | // If the amount of pointers in the source object is known and rather big, this setting could reduce allocations and improve performance. 19 | func WithExpectedPtrsCount(cnt int) func(*config) { 20 | return func(c *config) { 21 | c.expectedPointersCount = cnt 22 | } 23 | } 24 | 25 | type unexportedStrategy int 26 | 27 | const ( 28 | shallowCopyUnexportedStrategy unexportedStrategy = iota 29 | forceDeepCopyUnexportedStrategy 30 | forceZeroUnexported 31 | ) 32 | 33 | // WithForceUnexported is a functional option for the Clone function. When this setting is enabled, unexported fields will be cloned forcefully. 34 | func WithForceUnexported() func(*config) { 35 | return func(c *config) { 36 | c.unexportedStrategy = forceDeepCopyUnexportedStrategy 37 | } 38 | } 39 | 40 | // WithZeroUnexported is a functional option for the Clone function. When this setting is enabled, unexported fields will be forcefully set to zero value of their kind. 41 | func WithZeroUnexported() func(*config) { 42 | return func(c *config) { 43 | c.unexportedStrategy = forceZeroUnexported 44 | } 45 | } 46 | 47 | // WithErrOnUnsupported is a functional option for the Clone function. When this setting is enabled, attempting to clone channels and functions, even if they are nested, will result in an error. 48 | func WithErrOnUnsupported() func(*config) { 49 | return func(c *config) { 50 | c.errOnUnsuported = true 51 | } 52 | } 53 | 54 | // cloneCtx contains settings and pointers to values mapping for single clone 55 | type cloneCtx struct { 56 | ptrs map[unsafe.Pointer]reflect.Value 57 | unexportedStrategy unexportedStrategy 58 | errOnUnsuported bool 59 | } 60 | 61 | func (ctx *cloneCtx) resolvePointer(ptr unsafe.Pointer) (reflect.Value, bool) { 62 | v, ok := ctx.ptrs[ptr] 63 | return v, ok 64 | } 65 | 66 | func (ctx *cloneCtx) setPointer(ptr unsafe.Pointer, value reflect.Value) { 67 | ctx.ptrs[ptr] = value 68 | } 69 | 70 | // Clone returns a deep copy of passed argument. The cloned value will be identical to the source value. 71 | // Cloned pointers will point to a new object with the same value as in the source. 72 | // This also works for nested values, such as parts of collections (arrays, slices, and maps) or fields of passed structs. 73 | // It additionally guarantied that if two or more pointers at the source object references to the same value, clones pointers will refers to same vallue. 74 | // But if two or more slices at the source object refer to the same memory area (i.e. the same underlying array), the copy will refer to different arrays. 75 | // By default, unexported struct fields will be shallow copied, but this can be changed with functional options. 76 | // Unsupported types (channels and funcs) will be shallow copied by default, but this can also be changed with functional options. 77 | // Unsafe pointers will be treated like ordinary values. 78 | func Clone[T any](src T, opts ...funcOptions) (T, error) { 79 | // build a clone config 80 | cfg := config{} 81 | for _, o := range opts { 82 | o(&cfg) 83 | } 84 | 85 | // clone ctx will be passet to any recursive call cloneNested object 86 | // it sontains settings and a pointers to values mapping 87 | ctx := &cloneCtx{ 88 | ptrs: make(map[unsafe.Pointer]reflect.Value, cfg.expectedPointersCount), 89 | unexportedStrategy: cfg.unexportedStrategy, 90 | errOnUnsuported: cfg.errOnUnsuported, 91 | } 92 | 93 | // a shallow copy of the source was already made at the moment of passing it as an argument to a Copy function 94 | // so make an adressable reflect value for it 95 | valAtPtr := reflect.ValueOf(&src).Elem() 96 | // and recursively traverse it for deep copying its parts if needed 97 | err := cloneNested(ctx, valAtPtr) 98 | return src, err 99 | } 100 | 101 | func cloneNested(ctx *cloneCtx, v reflect.Value) error { 102 | kind := v.Kind() 103 | // check if the value should be copied, i.e. it's neither of basic type, nor zero 104 | if isBasicKind(kind) || v.IsZero() { 105 | return nil 106 | } 107 | // handle according to original's Kind 108 | switch kind { 109 | case reflect.Struct: 110 | // for structs iterate over it's fields 111 | for i := 0; i < v.NumField(); i++ { 112 | wField := v.Field(i) 113 | 114 | // if it can be set (exported) 115 | if wField.CanSet() { 116 | // recursively clone it 117 | if err := cloneNested(ctx, wField); err != nil { 118 | return err 119 | } 120 | continue 121 | } 122 | 123 | // if it is unexported it can be treaten according to on of following strategies 124 | switch ctx.unexportedStrategy { 125 | // do nothing (i.e shallow copy) 126 | case shallowCopyUnexportedStrategy: 127 | continue 128 | // forcely deep copy it 129 | case forceDeepCopyUnexportedStrategy: 130 | newAt := reflect.NewAt(wField.Type(), unsafe.Pointer(wField.UnsafeAddr())) 131 | if err := cloneNested(ctx, newAt.Elem()); err != nil { 132 | return err 133 | } 134 | // forcely turn it to zero value 135 | case forceZeroUnexported: 136 | typ := wField.Type() 137 | newAt := reflect.NewAt(typ, unsafe.Pointer(wField.UnsafeAddr())) 138 | newAt.Elem().Set(reflect.Zero(typ)) 139 | } 140 | } 141 | case reflect.Array: 142 | // for arrays allocate the new one of the elem type 143 | elem := v.Type().Elem() 144 | res := reflect.New(reflect.ArrayOf(v.Len(), elem)).Elem() 145 | // and copy values from source at once 146 | reflect.Copy(res, v) 147 | 148 | // if an elem kind is basic just return 149 | if isBasicKind(elem.Kind()) { 150 | v.Set(res) 151 | return nil 152 | } 153 | 154 | // otherwise recursively clone the elems 155 | for i := 0; i < res.Len(); i++ { 156 | if err := cloneNested(ctx, res.Index(i)); err != nil { 157 | return err 158 | } 159 | } 160 | 161 | // replace the source with the copy 162 | v.Set(res) 163 | case reflect.Slice: 164 | // for slices allocate the new one of the elem type 165 | typ := v.Type() 166 | res := reflect.MakeSlice(typ, v.Len(), v.Cap()) 167 | // and copy values from source at once 168 | reflect.Copy(res, v) 169 | 170 | // if an elem kind is basic just return 171 | if isBasicKind(typ.Elem().Kind()) { 172 | v.Set(res) 173 | return nil 174 | } 175 | 176 | // otherwise recursively clone the elems 177 | for i := 0; i < res.Len(); i++ { 178 | if err := cloneNested(ctx, res.Index(i)); err != nil { 179 | return err 180 | } 181 | } 182 | 183 | // replace the source with the copy 184 | v.Set(res) 185 | case reflect.Map: 186 | // for maps allocate the new one of the source type 187 | typ := v.Type() 188 | res := reflect.MakeMapWithSize(typ, v.Len()) 189 | 190 | // create new values for iterating over the map 191 | iter := v.MapRange() 192 | newK := reflect.New(typ.Key()).Elem() 193 | newV := reflect.New(typ.Elem()).Elem() 194 | 195 | // check if key and value maps are of basic type just once 196 | keyIsBasic := isBasicKind(typ.Key().Kind()) 197 | valueIsBasic := isBasicKind(typ.Elem().Kind()) 198 | 199 | for iter.Next() { 200 | k := iter.Key() 201 | // if key needs to be copied, copy it recursively 202 | if !keyIsBasic && !k.IsZero() { 203 | newK.Set(k) 204 | if err := cloneNested(ctx, newK); err != nil { 205 | return err 206 | } 207 | k = newK 208 | } 209 | v := iter.Value() 210 | // if value needs to be copied, copy it recursively 211 | if !valueIsBasic && !k.IsZero() { 212 | newV.Set(v) 213 | if err := cloneNested(ctx, newV); err != nil { 214 | return err 215 | } 216 | v = newV 217 | } 218 | // put key and value to a map copy 219 | res.SetMapIndex(k, v) 220 | } 221 | 222 | // replace the source with the copy 223 | v.Set(res) 224 | case reflect.Pointer: 225 | // check if pointer has already been met 226 | ptr := v.UnsafePointer() 227 | if newV, ok := ctx.resolvePointer(ptr); ok { 228 | // if it has been use previous copy 229 | v.Set(newV) 230 | return nil 231 | } 232 | 233 | // if this pointer has not been met create a new value 234 | newV := reflect.New(v.Elem().Type()) 235 | // and put it to context 236 | ctx.setPointer(ptr, newV) 237 | 238 | // put the source value to new pointer and recursively copy it 239 | newV.Elem().Set(v.Elem()) 240 | if err := cloneNested(ctx, newV.Elem()); err != nil { 241 | return err 242 | } 243 | // replace the source with the copy 244 | v.Set(newV) 245 | case reflect.Interface: 246 | // if interface underlying value needs to be copied 247 | el := v.Elem() 248 | if isBasicKind(el.Kind()) || el.IsZero() { 249 | return nil 250 | } 251 | // copy it recursively 252 | newV := reflect.New(v.Elem().Type()).Elem() 253 | newV.Set(v.Elem()) 254 | if err := cloneNested(ctx, newV); err != nil { 255 | return err 256 | } 257 | v.Set(newV) 258 | default: 259 | // if unsupported type strategy has been setted to err - return an error 260 | if ctx.errOnUnsuported { 261 | return fmt.Errorf("unsupported type: %s", v.Type().Name()) 262 | } 263 | } 264 | 265 | return nil 266 | } 267 | 268 | func isBasicKind(k reflect.Kind) bool { 269 | switch k { 270 | case 271 | reflect.Bool, 272 | reflect.Int, 273 | reflect.Int8, 274 | reflect.Int16, 275 | reflect.Int32, 276 | reflect.Int64, 277 | reflect.Uint, 278 | reflect.Uint8, 279 | reflect.Uint16, 280 | reflect.Uint32, 281 | reflect.Uint64, 282 | reflect.Uintptr, 283 | reflect.Float32, 284 | reflect.Float64, 285 | reflect.Complex64, 286 | reflect.Complex128, 287 | reflect.String, 288 | reflect.UnsafePointer: 289 | return true 290 | default: 291 | return false 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /clone_test.go: -------------------------------------------------------------------------------- 1 | package kamino_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | "unsafe" 8 | 9 | "github.com/stretchr/testify/assert" 10 | 11 | "github.com/LastPossum/kamino" 12 | ) 13 | 14 | func ExampleClone() { 15 | tests := []any{ 16 | 200000, 17 | `units are ready, with a`, 18 | 1000000, 19 | []string{ 20 | "more well", 21 | "on the way.", 22 | }, 23 | map[string]any{ 24 | "I don't like sand": "It's coarse and rough and irritating and it gets everywhere.", 25 | }, 26 | } 27 | 28 | for _, expected := range tests { 29 | actual, err := kamino.Clone(expected, kamino.WithErrOnUnsupported()) 30 | if err != nil { 31 | fmt.Println("got error:", err) 32 | } 33 | fmt.Println(actual) 34 | } 35 | // Output: 36 | // 200000 37 | // units are ready, with a 38 | // 1000000 39 | // [more well on the way.] 40 | // map[I don't like sand:It's coarse and rough and irritating and it gets everywhere.] 41 | } 42 | 43 | type simpleStruct struct { 44 | Int int 45 | Float64 float64 46 | String string 47 | } 48 | 49 | type alltogether struct { 50 | Bool bool 51 | Int int 52 | Float64 float64 53 | String string 54 | ArrayOfInt [10]int 55 | ArrayOfSimpleStruct [5]simpleStruct 56 | SliceOfInt []int 57 | SliceOfSimpleStruct []simpleStruct 58 | Nested simpleStruct 59 | 60 | IntPtrs []*int 61 | StructPtrs []*simpleStruct 62 | } 63 | 64 | var ( 65 | intInstance1 = 1114 66 | intInstance2 = 1387 67 | 68 | simpleStructIntance = simpleStruct{-1, -1, "-1"} 69 | 70 | alltogetherInstance = alltogether{ 71 | Bool: true, 72 | Int: 10, 73 | Float64: 20., 74 | String: "30", 75 | ArrayOfInt: [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, 76 | ArrayOfSimpleStruct: [5]simpleStruct{ 77 | {1, 1, "1"}, 78 | {2, 2, "2"}, 79 | {3, 3, "3"}, 80 | {4, 4, "4"}, 81 | {5, 5, "5"}, 82 | }, 83 | SliceOfInt: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, 84 | SliceOfSimpleStruct: []simpleStruct{ 85 | {1, 1, "1"}, 86 | {2, 2, "2"}, 87 | {3, 3, "3"}, 88 | {4, 4, "4"}, 89 | {5, 5, "5"}, 90 | }, 91 | Nested: simpleStruct{ 92 | 1, 2, "3", 93 | }, 94 | IntPtrs: []*int{&intInstance1, &intInstance2, &intInstance1}, 95 | StructPtrs: []*simpleStruct{&simpleStructIntance, &simpleStructIntance}, 96 | } 97 | ) 98 | 99 | func TestClone2(t *testing.T) { 100 | t.Run("primitive types", func(t *testing.T) { 101 | var ( 102 | i64 int64 = 1 103 | f64 float64 = 1.0 104 | b = true 105 | c128 complex128 = 4i + 1 106 | s = "blah-blah" 107 | ) 108 | 109 | goti64, _ := kamino.Clone(i64) 110 | assert.Equal(t, i64, goti64) 111 | 112 | gotf64, _ := kamino.Clone(f64) 113 | assert.Equal(t, f64, gotf64) 114 | 115 | gotb, _ := kamino.Clone(b) 116 | assert.Equal(t, b, gotb) 117 | 118 | gotc128, _ := kamino.Clone(c128) 119 | assert.Equal(t, c128, gotc128) 120 | 121 | gots, _ := kamino.Clone(s) 122 | assert.Equal(t, s, gots) 123 | }) 124 | 125 | t.Run("arrays", func(t *testing.T) { 126 | arrInts := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} 127 | 128 | arrIntsClone, _ := kamino.Clone(arrInts) 129 | assert.Equal(t, arrInts, arrIntsClone) 130 | 131 | arrAny := [...]any{0, 1, 2, 3, nil, 5, 6, &simpleStructIntance, 8, 9, 10, 11, 12, 13, 14, 15} 132 | 133 | gotArrAny, _ := kamino.Clone(arrAny) 134 | assert.Equal(t, arrAny, gotArrAny) 135 | }) 136 | 137 | t.Run("slices", func(t *testing.T) { 138 | sliceInts := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} 139 | 140 | sliceIntsClone, _ := kamino.Clone(sliceInts) 141 | assert.Equal(t, sliceInts, sliceIntsClone) 142 | 143 | sliceAny := []any{0, 1, 2, 3, nil, 5, 6, &simpleStructIntance, 8, 9, 10, 11, 12, 13, 14, 15} 144 | 145 | gotSliceAny, _ := kamino.Clone(sliceAny) 146 | assert.Equal(t, sliceAny, gotSliceAny) 147 | 148 | sliceOfSlice := [][]int{{1, 2, 4}, {4, 5}, {6}} 149 | gotSliceOfSlice, _ := kamino.Clone(sliceOfSlice) 150 | assert.Equal(t, sliceOfSlice, gotSliceOfSlice) 151 | sliceOfSlice[0][1] = 7 152 | assert.NotEqual(t, sliceOfSlice, gotSliceOfSlice) 153 | }) 154 | 155 | t.Run("map", func(t *testing.T) { 156 | m := make(map[string]int) 157 | m["1"] = 1 158 | m["2"] = 2 159 | m["3"] = 3 160 | 161 | got, _ := kamino.Clone(m) 162 | assert.Equal(t, m, got) 163 | 164 | m2 := make(map[[3]int][]int) 165 | m2[[3]int{1, 2, 3}] = []int{1, 2, 3} 166 | m2[[3]int{2, 3}] = []int{2, 3} 167 | m2[[3]int{3}] = []int{3} 168 | 169 | got2, _ := kamino.Clone(m2) 170 | assert.Equal(t, m2, got2) 171 | 172 | i := 1 173 | m3 := map[string]*int{ 174 | "1": &i, 175 | } 176 | got3, _ := kamino.Clone(m3) 177 | assert.Equal(t, m3, got3) 178 | 179 | *got3["1"] = 10 180 | assert.NotEqual(t, m3, got3) 181 | }) 182 | 183 | t.Run("simple struct", func(t *testing.T) { 184 | type simpleStruct struct { 185 | Int int 186 | Float64 float64 187 | String string 188 | } 189 | param := simpleStruct{ 190 | 10, 20., "30", 191 | } 192 | 193 | got, _ := kamino.Clone(param) 194 | assert.Equal(t, param, got) 195 | }) 196 | 197 | t.Run("pointer", func(t *testing.T) { 198 | var ( 199 | i64 int64 = 1 200 | sstruct = simpleStruct{2, 3, "4"} 201 | 202 | i64Ptr = &i64 203 | sstructPtr = &sstruct 204 | ) 205 | 206 | i64PtrClone, _ := kamino.Clone(i64Ptr) 207 | assert.Equal(t, *i64Ptr, *i64PtrClone) 208 | 209 | *i64PtrClone = -1 210 | assert.Equal(t, *i64Ptr, int64(1)) 211 | 212 | sstructPtrClone, _ := kamino.Clone(sstructPtr) 213 | assert.Equal(t, sstructPtr, sstructPtrClone) 214 | 215 | sstructPtrClone.Int = -2 216 | sstructPtrClone.Float64 = -3 217 | sstructPtrClone.String = "-4" 218 | 219 | assert.Equal(t, sstructPtr.Int, 2) 220 | assert.Equal(t, sstructPtr.Float64, float64(3)) 221 | assert.Equal(t, sstructPtr.String, "4") 222 | }) 223 | 224 | t.Run("ptrCircles", func(t *testing.T) { 225 | type circled struct { 226 | PtrA *int 227 | PtrB *int 228 | 229 | This *circled 230 | } 231 | 232 | i := 10 233 | c := circled{PtrA: &i, PtrB: &i} 234 | c.This = &c 235 | 236 | cPtrClone, _ := kamino.Clone(&c) 237 | 238 | assert.Equal(t, cPtrClone, &c) 239 | assert.Equal(t, unsafe.Pointer(cPtrClone), unsafe.Pointer(cPtrClone.This)) 240 | assert.Equal(t, unsafe.Pointer(cPtrClone.PtrA), unsafe.Pointer(cPtrClone.PtrB)) 241 | }) 242 | 243 | t.Run("alltogether", func(t *testing.T) { 244 | got, _ := kamino.Clone(alltogetherInstance) 245 | assert.Equal(t, alltogetherInstance, got) 246 | }) 247 | 248 | t.Run("original is not affected by clone mutation", func(t *testing.T) { 249 | original := alltogether{ 250 | Int: 1, 251 | Float64: 2, 252 | String: "3", 253 | ArrayOfInt: [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, 254 | SliceOfInt: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, 255 | Nested: simpleStruct{1, 2, "3"}, 256 | } 257 | 258 | originalHandCopy := alltogether{ 259 | Int: 1, 260 | Float64: 2, 261 | String: "3", 262 | ArrayOfInt: [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, 263 | SliceOfInt: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, 264 | Nested: simpleStruct{1, 2, "3"}, 265 | } 266 | assert.Equal(t, original, originalHandCopy) 267 | 268 | clone, err := kamino.Clone(original) 269 | assert.NoError(t, err) 270 | 271 | assert.Equal(t, original, clone) 272 | assert.Equal(t, original, originalHandCopy) 273 | 274 | clone.Int = 2 275 | clone.Float64 = 3 276 | clone.String = "another string" 277 | clone.ArrayOfInt[5] = -1 278 | clone.SliceOfInt[5] = -5 279 | 280 | assert.NotEqual(t, original, clone) 281 | assert.Equal(t, original, originalHandCopy) 282 | }) 283 | } 284 | 285 | func TestInterface(t *testing.T) { 286 | x := []any{nil} 287 | y, _ := kamino.Clone(x) 288 | assert.Equal(t, x, y) 289 | 290 | var a any 291 | b, _ := kamino.Clone(a) 292 | assert.True(t, a == b) 293 | } 294 | 295 | func TestAny(t *testing.T) { 296 | x := any(simpleStructIntance) 297 | y, _ := kamino.Clone(x) 298 | assert.Equal(t, x, y) 299 | 300 | ys, ok := y.(simpleStruct) 301 | assert.True(t, ok) 302 | assert.Equal(t, ys, simpleStructIntance) 303 | } 304 | 305 | func TestTwoNils(t *testing.T) { 306 | type Foo struct { 307 | A int 308 | } 309 | type Bar struct { 310 | B int 311 | } 312 | type FooBar struct { 313 | Foo *Foo 314 | Bar *Bar 315 | Foo2 *Foo 316 | Bar2 *Bar 317 | } 318 | 319 | src := &FooBar{ 320 | Foo2: &Foo{1}, 321 | Bar2: &Bar{2}, 322 | } 323 | 324 | dst, _ := kamino.Clone(src) 325 | 326 | assert.Equal(t, src, dst) 327 | } 328 | 329 | func ptrTo[T any](v T) *T { 330 | return &v 331 | } 332 | 333 | func TestCloneUnexported(t *testing.T) { 334 | type Foo struct { 335 | A int 336 | a int 337 | Ptr1 *int 338 | ptr2 *int 339 | } 340 | 341 | foo := Foo{ 342 | 1, 2, ptrTo(3), ptrTo(4), 343 | } 344 | 345 | bar, _ := kamino.Clone(foo) 346 | assert.Equal(t, foo, bar) 347 | 348 | assert.NotEqual(t, unsafe.Pointer(foo.Ptr1), unsafe.Pointer(bar.Ptr1)) 349 | assert.Equal(t, unsafe.Pointer(foo.ptr2), unsafe.Pointer(bar.ptr2)) 350 | } 351 | 352 | func TestForceCloneUnexported(t *testing.T) { 353 | type Foo struct { 354 | A int 355 | a int 356 | Ptr1 *int 357 | ptr2 *int 358 | } 359 | 360 | foo := Foo{ 361 | 1, 2, ptrTo(3), ptrTo(4), 362 | } 363 | 364 | bar, _ := kamino.Clone(foo, kamino.WithForceUnexported()) 365 | assert.Equal(t, foo, bar) 366 | 367 | assert.NotEqual(t, unsafe.Pointer(foo.Ptr1), unsafe.Pointer(bar.Ptr1)) 368 | assert.NotEqual(t, unsafe.Pointer(foo.ptr2), unsafe.Pointer(bar.ptr2)) 369 | } 370 | 371 | func TestForceZeroUnexported(t *testing.T) { 372 | type Foo struct { 373 | A int 374 | a int 375 | Ptr1 *int 376 | ptr2 *int 377 | } 378 | 379 | foo := Foo{ 380 | 1, 2, ptrTo(3), ptrTo(4), 381 | } 382 | 383 | bar, _ := kamino.Clone(foo, kamino.WithZeroUnexported()) 384 | assert.Equal(t, foo.A, bar.A) 385 | assert.Equal(t, foo.Ptr1, bar.Ptr1) 386 | 387 | assert.Equal(t, 2, foo.a) 388 | assert.Equal(t, ptrTo(4), foo.ptr2) 389 | 390 | var nilintPtr *int 391 | assert.Equal(t, 0, bar.a) 392 | assert.Equal(t, nilintPtr, bar.ptr2) 393 | } 394 | 395 | type Fooer interface { 396 | foo() int 397 | } 398 | 399 | type fooer struct { 400 | i int 401 | } 402 | 403 | func (f *fooer) foo() int { 404 | return f.i 405 | } 406 | 407 | func TestCopyInterface(t *testing.T) { 408 | type fooerWrapper struct { 409 | F Fooer 410 | } 411 | 412 | fi := &fooer{i: 10} 413 | 414 | fw := fooerWrapper{ 415 | F: fi, 416 | } 417 | 418 | got, _ := kamino.Clone(fw) 419 | 420 | assert.Equal(t, got, fw) 421 | fi.i = 20 422 | assert.NotEqual(t, got.F.foo(), fw.F.foo()) 423 | } 424 | 425 | func TestCopyNestedTime(t *testing.T) { 426 | type nestedTime struct { 427 | T time.Time 428 | } 429 | 430 | nt := nestedTime{time.Now()} 431 | got, _ := kamino.Clone(nt) 432 | 433 | assert.Equal(t, got.T, nt.T) 434 | } 435 | 436 | func TestCopyNestedNil(t *testing.T) { 437 | type nestedNil struct { 438 | X any 439 | } 440 | 441 | nn := nestedNil{} 442 | got, _ := kamino.Clone(nn) 443 | 444 | assert.Equal(t, got, nn) 445 | } 446 | 447 | func TestWithErrOnUnsupported(t *testing.T) { 448 | t.Run("errOnUnsupported chans", func(t *testing.T) { 449 | f := func() {} 450 | 451 | _, err := kamino.Clone(f) 452 | assert.NoError(t, err) 453 | 454 | _, err = kamino.Clone(f, kamino.WithErrOnUnsupported()) 455 | assert.Error(t, err) 456 | }) 457 | 458 | t.Run("errOnUnsupported funcs", func(t *testing.T) { 459 | ch := make(chan int) 460 | 461 | _, err := kamino.Clone(ch) 462 | assert.NoError(t, err) 463 | 464 | _, err = kamino.Clone(ch, kamino.WithErrOnUnsupported()) 465 | assert.Error(t, err) 466 | }) 467 | 468 | t.Run("errOnUnsupported ptrs", func(t *testing.T) { 469 | ch := make(chan int) 470 | f := func() {} 471 | 472 | _, err := kamino.Clone(ch, kamino.WithErrOnUnsupported()) 473 | assert.Error(t, err) 474 | 475 | _, err = kamino.Clone(f, kamino.WithErrOnUnsupported()) 476 | assert.Error(t, err) 477 | }) 478 | 479 | t.Run("errOnUnsupported with suported only", func(t *testing.T) { 480 | _, err := kamino.Clone(alltogetherInstance, kamino.WithErrOnUnsupported()) 481 | assert.NoError(t, err) 482 | }) 483 | 484 | t.Run("errOnUnsupported in structs", func(t *testing.T) { 485 | type foo struct { 486 | F func() 487 | } 488 | 489 | f := foo{F: func() {}} 490 | 491 | _, err := kamino.Clone(f) 492 | assert.NoError(t, err) 493 | 494 | _, err = kamino.Clone(f, kamino.WithErrOnUnsupported()) 495 | assert.Error(t, err) 496 | 497 | _, err = kamino.Clone(&f, kamino.WithErrOnUnsupported()) 498 | assert.Error(t, err) 499 | }) 500 | 501 | t.Run("errOnUnsupported unexported in structs", func(t *testing.T) { 502 | type foo struct { 503 | f func() 504 | } 505 | 506 | f := foo{f: func() {}} 507 | 508 | _, err := kamino.Clone(f) 509 | assert.NoError(t, err) 510 | 511 | _, err = kamino.Clone(f, kamino.WithErrOnUnsupported()) 512 | assert.NoError(t, err) 513 | 514 | _, err = kamino.Clone(f, kamino.WithErrOnUnsupported(), kamino.WithForceUnexported()) 515 | assert.Error(t, err) 516 | }) 517 | 518 | t.Run("errOnUnsupported in array", func(t *testing.T) { 519 | a := [...]any{1, "2", func() {}} 520 | 521 | _, err := kamino.Clone(a) 522 | assert.NoError(t, err) 523 | 524 | _, err = kamino.Clone(a, kamino.WithErrOnUnsupported()) 525 | assert.Error(t, err) 526 | }) 527 | 528 | t.Run("errOnUnsupported in slice", func(t *testing.T) { 529 | s := []any{1, "2", func() {}} 530 | 531 | _, err := kamino.Clone(s) 532 | assert.NoError(t, err) 533 | 534 | _, err = kamino.Clone(s, kamino.WithErrOnUnsupported()) 535 | assert.Error(t, err) 536 | }) 537 | 538 | t.Run("errOnUnsupported in maps", func(t *testing.T) { 539 | s := map[string]any{"supported": 1, "unsuppotred": func() {}} 540 | 541 | _, err := kamino.Clone(s) 542 | assert.NoError(t, err) 543 | 544 | _, err = kamino.Clone(s, kamino.WithErrOnUnsupported()) 545 | assert.Error(t, err) 546 | }) 547 | } 548 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/LastPossum/kamino 2 | 3 | go 1.19 4 | 5 | require github.com/stretchr/testify v1.9.0 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.1 // indirect 9 | github.com/pmezard/go-difflib v1.0.0 // indirect 10 | gopkg.in/yaml.v3 v3.0.1 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 9 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 10 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 11 | --------------------------------------------------------------------------------