Good day!
TL;DR: When I have custom struct with at least one field of type time.Time and I use goderive to generate DeepCopy implementation of that struct, the generated code tries to access private types of time package. This leads into code that does not compile!
If I made anything wrong, please ping me. Perhaps, I missed something important in documetation, or it is misuse... Thanks in advance
How to get to this state
Start Go module from scratch and add goderive as tool for Go module
go get -tool github.com/awalterschulze/goderive
Now we have in go.mod file:
module 15-goderive-issue
go 1.24
tool github.com/awalterschulze/goderive
require (
github.com/awalterschulze/goderive v0.5.1 // indirect
github.com/kisielk/gotool v1.0.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/tools v0.6.0 // indirect
)
And let us introduce a small example with Xyz structure having field of type time.Time, so deep copy can be generated:
// main.go
package main
import (
"fmt"
"time"
)
type Xyz struct {
timestamp time.Time
}
func (xyz *Xyz) Clone() *Xyz {
if xyz == nil {
return nil
}
var clone Xyz
deriveDeepCopyXyz(&clone, xyz)
return &clone
}
func main() {
xyz := &Xyz{}
fmt.Println(xyz)
}
Generate the "deep copy" using:
Generated code
Whole generated file is:
// Code generated by goderive DO NOT EDIT.
package main
import (
"reflect"
"time"
"unsafe"
)
// deriveDeepCopyXyz recursively copies the contents of src into dst.
func deriveDeepCopyXyz(dst, src *Xyz) {
func() {
field := new(time.Time)
deriveDeepCopy(field, &src.timestamp)
dst.timestamp = *field
}()
}
// deriveDeepCopy recursively copies the contents of src into dst.
func deriveDeepCopy(dst, src *time.Time) {
src_v := reflect.Indirect(reflect.ValueOf(src))
dst_v := reflect.Indirect(reflect.ValueOf(dst))
*(*uint64)(unsafe.Pointer(dst_v.FieldByName("wall").UnsafeAddr())) = *(*uint64)(unsafe.Pointer(src_v.FieldByName("wall").UnsafeAddr()))
*(*int64)(unsafe.Pointer(dst_v.FieldByName("ext").UnsafeAddr())) = *(*int64)(unsafe.Pointer(src_v.FieldByName("ext").UnsafeAddr()))
if *(**time.Location)(unsafe.Pointer(src_v.FieldByName("loc").UnsafeAddr())) == nil {
*(**time.Location)(unsafe.Pointer(dst_v.FieldByName("loc").UnsafeAddr())) = nil
} else {
*(**time.Location)(unsafe.Pointer(dst_v.FieldByName("loc").UnsafeAddr())) = new(time.Location)
deriveDeepCopy_(*(**time.Location)(unsafe.Pointer(dst_v.FieldByName("loc").UnsafeAddr())), *(**time.Location)(unsafe.Pointer(src_v.FieldByName("loc").UnsafeAddr())))
}
}
// deriveDeepCopy_ recursively copies the contents of src into dst.
func deriveDeepCopy_(dst, src *time.Location) {
src_v := reflect.Indirect(reflect.ValueOf(src))
dst_v := reflect.Indirect(reflect.ValueOf(dst))
*(*string)(unsafe.Pointer(dst_v.FieldByName("name").UnsafeAddr())) = *(*string)(unsafe.Pointer(src_v.FieldByName("name").UnsafeAddr()))
if *(*[]time.zone)(unsafe.Pointer(src_v.FieldByName("zone").UnsafeAddr())) == nil {
*(*[]time.zone)(unsafe.Pointer(dst_v.FieldByName("zone").UnsafeAddr())) = nil
} else {
if *(*[]time.zone)(unsafe.Pointer(dst_v.FieldByName("zone").UnsafeAddr())) != nil {
if len(*(*[]time.zone)(unsafe.Pointer(src_v.FieldByName("zone").UnsafeAddr()))) > len(*(*[]time.zone)(unsafe.Pointer(dst_v.FieldByName("zone").UnsafeAddr()))) {
if cap(*(*[]time.zone)(unsafe.Pointer(dst_v.FieldByName("zone").UnsafeAddr()))) >= len(*(*[]time.zone)(unsafe.Pointer(src_v.FieldByName("zone").UnsafeAddr()))) {
*(*[]time.zone)(unsafe.Pointer(dst_v.FieldByName("zone").UnsafeAddr())) = (*(*[]time.zone)(unsafe.Pointer(dst_v.FieldByName("zone").UnsafeAddr())))[:len(*(*[]time.zone)(unsafe.Pointer(src_v.FieldByName("zone").UnsafeAddr())))]
} else {
*(*[]time.zone)(unsafe.Pointer(dst_v.FieldByName("zone").UnsafeAddr())) = make([]time.zone, len(*(*[]time.zone)(unsafe.Pointer(src_v.FieldByName("zone").UnsafeAddr()))))
}
} else if len(*(*[]time.zone)(unsafe.Pointer(src_v.FieldByName("zone").UnsafeAddr()))) < len(*(*[]time.zone)(unsafe.Pointer(dst_v.FieldByName("zone").UnsafeAddr()))) {
*(*[]time.zone)(unsafe.Pointer(dst_v.FieldByName("zone").UnsafeAddr())) = (*(*[]time.zone)(unsafe.Pointer(dst_v.FieldByName("zone").UnsafeAddr())))[:len(*(*[]time.zone)(unsafe.Pointer(src_v.FieldByName("zone").UnsafeAddr())))]
}
} else {
*(*[]time.zone)(unsafe.Pointer(dst_v.FieldByName("zone").UnsafeAddr())) = make([]time.zone, len(*(*[]time.zone)(unsafe.Pointer(src_v.FieldByName("zone").UnsafeAddr()))))
}
copy(*(*[]time.zone)(unsafe.Pointer(dst_v.FieldByName("zone").UnsafeAddr())), *(*[]time.zone)(unsafe.Pointer(src_v.FieldByName("zone").UnsafeAddr())))
}
if *(*[]time.zoneTrans)(unsafe.Pointer(src_v.FieldByName("tx").UnsafeAddr())) == nil {
*(*[]time.zoneTrans)(unsafe.Pointer(dst_v.FieldByName("tx").UnsafeAddr())) = nil
} else {
if *(*[]time.zoneTrans)(unsafe.Pointer(dst_v.FieldByName("tx").UnsafeAddr())) != nil {
if len(*(*[]time.zoneTrans)(unsafe.Pointer(src_v.FieldByName("tx").UnsafeAddr()))) > len(*(*[]time.zoneTrans)(unsafe.Pointer(dst_v.FieldByName("tx").UnsafeAddr()))) {
if cap(*(*[]time.zoneTrans)(unsafe.Pointer(dst_v.FieldByName("tx").UnsafeAddr()))) >= len(*(*[]time.zoneTrans)(unsafe.Pointer(src_v.FieldByName("tx").UnsafeAddr()))) {
*(*[]time.zoneTrans)(unsafe.Pointer(dst_v.FieldByName("tx").UnsafeAddr())) = (*(*[]time.zoneTrans)(unsafe.Pointer(dst_v.FieldByName("tx").UnsafeAddr())))[:len(*(*[]time.zoneTrans)(unsafe.Pointer(src_v.FieldByName("tx").UnsafeAddr())))]
} else {
*(*[]time.zoneTrans)(unsafe.Pointer(dst_v.FieldByName("tx").UnsafeAddr())) = make([]time.zoneTrans, len(*(*[]time.zoneTrans)(unsafe.Pointer(src_v.FieldByName("tx").UnsafeAddr()))))
}
} else if len(*(*[]time.zoneTrans)(unsafe.Pointer(src_v.FieldByName("tx").UnsafeAddr()))) < len(*(*[]time.zoneTrans)(unsafe.Pointer(dst_v.FieldByName("tx").UnsafeAddr()))) {
*(*[]time.zoneTrans)(unsafe.Pointer(dst_v.FieldByName("tx").UnsafeAddr())) = (*(*[]time.zoneTrans)(unsafe.Pointer(dst_v.FieldByName("tx").UnsafeAddr())))[:len(*(*[]time.zoneTrans)(unsafe.Pointer(src_v.FieldByName("tx").UnsafeAddr())))]
}
} else {
*(*[]time.zoneTrans)(unsafe.Pointer(dst_v.FieldByName("tx").UnsafeAddr())) = make([]time.zoneTrans, len(*(*[]time.zoneTrans)(unsafe.Pointer(src_v.FieldByName("tx").UnsafeAddr()))))
}
copy(*(*[]time.zoneTrans)(unsafe.Pointer(dst_v.FieldByName("tx").UnsafeAddr())), *(*[]time.zoneTrans)(unsafe.Pointer(src_v.FieldByName("tx").UnsafeAddr())))
}
*(*string)(unsafe.Pointer(dst_v.FieldByName("extend").UnsafeAddr())) = *(*string)(unsafe.Pointer(src_v.FieldByName("extend").UnsafeAddr()))
*(*int64)(unsafe.Pointer(dst_v.FieldByName("cacheStart").UnsafeAddr())) = *(*int64)(unsafe.Pointer(src_v.FieldByName("cacheStart").UnsafeAddr()))
*(*int64)(unsafe.Pointer(dst_v.FieldByName("cacheEnd").UnsafeAddr())) = *(*int64)(unsafe.Pointer(src_v.FieldByName("cacheEnd").UnsafeAddr()))
if *(**time.zone)(unsafe.Pointer(src_v.FieldByName("cacheZone").UnsafeAddr())) == nil {
*(**time.zone)(unsafe.Pointer(dst_v.FieldByName("cacheZone").UnsafeAddr())) = nil
} else {
*(**time.zone)(unsafe.Pointer(dst_v.FieldByName("cacheZone").UnsafeAddr())) = new(time.zone)
**(**time.zone)(unsafe.Pointer(dst_v.FieldByName("cacheZone").UnsafeAddr())) = **(**time.zone)(unsafe.Pointer(src_v.FieldByName("cacheZone").UnsafeAddr()))
}
}
And the problematic part is in deriveDeepCopy_, where are directly accessed private structs like time.zone and time.zoneTrans. This

Build process failing
Output from build:
# 15-goderive-issue
./derived.gen.go:39:15: name zone not exported by package time
./derived.gen.go:40:13: name zone not exported by package time
./derived.gen.go:42:16: name zone not exported by package time
./derived.gen.go:43:21: name zone not exported by package time
./derived.gen.go:44:22: name zone not exported by package time
./derived.gen.go:45:16: name zone not exported by package time
./derived.gen.go:47:92: name zone not exported by package time
./derived.gen.go:49:28: name zone not exported by package time
./derived.gen.go:50:15: name zone not exported by package time
./derived.gen.go:53:90: name zone not exported by package time
./derived.gen.go:53:90: too many errors
Good day!
TL;DR: When I have custom struct with at least one field of type
time.Timeand I use goderive to generate DeepCopy implementation of that struct, the generated code tries to access private types oftimepackage. This leads into code that does not compile!If I made anything wrong, please ping me. Perhaps, I missed something important in documetation, or it is misuse... Thanks in advance
How to get to this state
Start Go module from scratch and add goderive as tool for Go module
Now we have in
go.modfile:And let us introduce a small example with
Xyzstructure having field of typetime.Time, so deep copy can be generated:Generate the "deep copy" using:
Generated code
Whole generated file is:
And the problematic part is in
deriveDeepCopy_, where are directly accessed private structs liketime.zoneandtime.zoneTrans. ThisBuild process failing
Output from build: