-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proto: add function to shallow copy a message #1155
Comments
I have just had to abandon an upgrade because we copy messages. Because they now contain mutexes the copylocks check of the go vet command now fails in numerous places. |
The data structures that were not safe for shallow copying have always been present since v1.1 (and possibly earlier). The only difference is that we made it possible for vet to detect such cases more readily. In other words, just because vet didn't flag it for v1.3 and earlier doesn't mean it was okay on those versions. |
It isn't obvious to users of these libraries that it wasn't safe. The point of generated code is that a user shouldn't need to be reading it to figure out implementation subtleties. It is good that you've made everyone aware of the issue but... at the same time there's no way to remediate the problem. Edit: To make things worse, I have a lot of code that uses |
(Edited after I found the support to copy/comparison): C++ API, Python API and Java API provide The equivalent methods in Go are |
For future reference: You probably mean |
Question: proto.Clone() creates a new protobuff message from the old one. What If I do not want that? Instead I want to create a new struct that only contains the data fields without any protobuf fields (XXX and suchlike). In that case I want an explicit CopyFrom(), MergeFrom() methods ? proto.Clone() is not suitable in this case... It might be possible to use proto.Marshal()/Unmarsha() which gets you most of the way there albeit with the protobuf fields still present but nil.? |
😟 I don’t think something like that is going to be easily accomplished without using protobuf reflection. How could these hypothetical |
@puellanivis yes - I think this can only be accomplished if the generated code creates these methods as part of the struct definition... |
Mmm I found this topic because I was hoping for a better solution to shallow copy protobuf message(s). I'd guess you're right @puellanivis @eccles, in that the only way to accomplish "copy without reflection" would be to implement it manually, or generate code for it. My use case typically involves patching part of an existing message, without actually modifying the existing value. It's been my experience that treating messages as read-only often the only sane choice, if you're passing protobuf messages around (within a single process). As a point of comparison, I've added my implementation to the bottom of this comment, which is semantically similar to OP's but uses the EDIT: I did a little bit of benchmarking, and found the results quite interesting. Note that the manual implementations were effectively 1-1 with what the
// shallow copy methods could also be implemented manually, though that's a bit of a pain
var copySomeMessage = ProtoCopier((*SomeMessage)(nil))
func (x *SomeMessage) Copy() *SomeMessage { return copySomeMessage(x).(*SomeMessage) } // ProtoCopier returns a function that will shallow copy values of a given protobuf value's type, utilising any `Get`
// prefixed method, accepting no input parameters, and returning a single value of the same type, available for each
// given field, intended to be be used with generated code for protobuf messages
// NOTE a panic will occur if the v's type is not t
func ProtoCopier(v interface{}) func(v interface{}) interface{} {
var (
t = reflect.TypeOf(v)
fieldMethods = make([][2]int, 0)
)
for i := 0; i < t.Elem().NumField(); i++ {
field := t.Elem().Field(i)
if field.PkgPath != `` {
continue
}
method, ok := t.MethodByName(`Get` + field.Name)
if !ok ||
method.Type.NumIn() != 1 ||
method.Type.NumOut() != 1 ||
method.Type.Out(0) != field.Type {
continue
}
fieldMethods = append(fieldMethods, [2]int{i, method.Index})
}
return func(v interface{}) interface{} {
src := reflect.ValueOf(v)
protoCopierCheckType(t, src.Type())
dst := reflect.New(t.Elem()).Elem()
for _, fieldMethod := range fieldMethods {
dst.Field(fieldMethod[0]).Set(src.Method(fieldMethod[1]).Call(nil)[0])
}
return dst.Addr().Interface()
}
}
func protoCopierCheckType(dst, src reflect.Type) {
if dst != src {
panic(fmt.Errorf(`ProtoCopier dst %s != src %s`, dst, src))
}
} |
Generated messages have mutexes and other variables that make it dangerous to shallow copy.
Add a function to the
proto
package that does the equivalent of a shallow copy in a safe way.The function would semantically equivalent to:
but be optimized for performance.
The text was updated successfully, but these errors were encountered: