Hacker News new | past | comments | ask | show | jobs | submit login
Upcoming Go protobuf release (groups.google.com)
102 points by stablemap on Jan 30, 2018 | hide | past | favorite | 20 comments



The three breaking changes are interesting in how, one by one, they describe flaws in the Go language (and I say that as someone whose day job is Go development).

The first breaking change means unkeyed literals such as Foo{"bar"} no longer work. This particular Go feature (which arguably came from C) is so open to future compatibility breakage that I wish it had never been included. I prefer Rust's approach here, where fields always have to be speciifed, but a ::new() constructor can be furnished to make fieldless construction possible through the type (as opposed to Go's NewFoo() style which is semantically disconnected from the type).

The second one, about structs no longer being usable as map keys or comparable with ==, is a bit more esoteric, but it does demonstrate how Go's type system is too weak to allow such a change to be made without breakage. In Rust or C++, this would presumably have been accomplished with an equality trait, so that the new struct could be made comparable with ==.

The third one, about breaking reflect.DeepEqual(), also reveals how the lack of trait support at the language level exposes cracks in the language. When a user is forced to use a blunt tool such as DeepEqual(), which disconnects the equality code from the underlying type being used, then this is what you get. That said, I'm not sure why Protobuf can't optionally generate equality methods for the types it generates in the first place, which is something Gogo can do [1].

Go is a great language, but it's things like this that give me Rust envy.

[1] https://github.com/gogo/protobuf/blob/master/plugin/equal/eq...


"This particular Go feature (which arguably came from C) is so open to future compatibility breakage that I wish it had never been included."

This is the community consensus view. I strongly disagree with it. Unkeyed literals are useful for when you want to get a compile-time error if any fields change, which is common in tuple-like structs. If there is anything wrong with this, it is that in theory you ought to be able to specify at the type declaration whether you must use unkeyed (IMHO a valid use case), must use keyed, or may use either. However this would not generally fit with Go's philosophy.

As a consequence of that theory, I would never use unkeyed literals to create a generated type; too likely that something will change in the generated type with future generators, exactly as I stated.

I suppose this could be layered on in another linter, although since the community and I part ways here even if I wrote "nobody" would use it.

"The second one, about structs no longer being usable as map keys or comparable with ==,"

One of the weirdest things about Go to me is the whiff on making several operations interfaces, like how Python has __del__ and such. == is a particularly weird case; the rules for == are downright dynamic language-ish: https://golang.org/ref/spec#Comparison_operators. I imagine they may have considered this "operator overloading" but IMHO that's only a problem on operators that come with a lot of implicit semantics like +, not the "lookup operator".


Unkeyed literals are definitely useful. But the counterargument is that by making keyless field order part of the type, you're creating an implicit protocol, one that can easily be broken, and so it comes with downsides.

The odd thing is that Go permits fields to be omitted for the keyed literals, but not for the unkeyed ones. So keyed literals are less strict, as you point out, from the point of view that a caller should know what they're initializing, which is not possible when the evolution of a program mandates that new mandatory fields be added to a struct. I've been bitten by this so many times.†

One solution to your desired functionality would to let types declare a constructor that can accept ordered arguments. Then the key order could be part of that signature, and you could support it for all time. I can imagine a bunch of possible syntaxes that could work here.

___

† As an aside, Rust is super strict here: A struct literal must have keys, and all keys are mandatory, although its literal syntax supports splatting, so you can merge in a default empty version:

  let foo = Foo { something: 42, ..emptyFoo }
That gives you the best of both worlds, in my opinion.


Unkeyed literals wont produce a compiler error if fields of the same type and position are replaced. Or if a field is “renamed” to something semantically distinct.


I tend to use lots of little types, so I don't generally end up with {int, int, int} but {Width, Height, Depth} or something. In practice I don't have this problem. YMMV depending on your own practices.


The protobuf package does provide an Equal function:

https://github.com/golang/protobuf/blob/master/proto/equal.g...


It does, but a method would arguably have been better.


Pardon my ignorance, but is there any functional change by it being a method vs a func? All I can think of is not having to type in the package name to call it and possibly the ability to memoize parts within the struct itself but not sure if you'd even want that in a general solution. If the argument is one of those or aesthetic then awesome, but I'm wondering if I'm missing some difference in how Go handles them.


Mostly aesthetics and ergonomics. Proto messages have an interface [1], but equality is strangely not part of it. Having it be a method would make it discoverable — most editors/IDEs would have Equal() show up as an "intellisense" autocompletion if you typed "msg." or similar.

Having it be a package-level function means you have to read the documentation to find it. The fact that they single out reflect.DeepEquals() being used instead implies perhaps that there are people who didn't read the documentation.

You could also argue that if Equal itself were an interface, it could be extended to other code beyond Protobuf. Many languages do this.

Such an interface could also conceivably be leveraged by the language to support arbitrary objects as map keys and hashing in general. Rust does this [2], but requiring that a map key implement the traits Eq (which provides equality semantics throughout the language) and Hash. Traits are similar to Go interfaces.

(As an additional point of elegance, Rust can generate the implementation for you, via an annotation, so for many types you can simply write #[derive(Eq, Hash)] and now your type has "==" and hash() with no additional effort.)

I'm sure Go's designers wouldn't want to go down that rabbit hole, though. The only built-in interface I know about is "error", but the language itself doesn't know about it, and I don't think there are any interfaces that are leveraged by the language.

[1] https://godoc.org/github.com/golang/protobuf/proto#Message

[2] https://doc.rust-lang.org/std/collections/struct.HashMap.htm...


Nothing wrong with Go protobuf, but if you need a lean and mean Protocol Buffers implementation I can recommend Nanopb. It's just a few kB and you can run it even malloc free.

[1] http://jpa.kapsi.fi/nanopb/


If only I had known this existed last year... I linked in Google's C++ library compiled for ARM to a project and it bloated the binary to ~12 MB...


We use nanopb on embedded devices; it's quite small & efficient. No real complaints.


Hmm, this seems like it might create security leaks for developers using protobuf at public interfaces. They should probably have some kind of static configuration variable `DefaultDiscardUnknown = true`.


will this fix slow gRPC performance in Go compared to Java/C++ or that is different issue.


Do you have any sources about gRPC being slow in Go?


Not sure, but I saw this video: At 26:16 Gopherfest 2017: Upspin (Rob Pike) - https://www.youtube.com/watch?v=ENLWEfi0Tkg



I wonder how this compares with Gogoproto. Nobody in their sane mind uses the standard protobuf package anyway.


I've been using the standard protobuf package for years, yet I am entirely sane. My mother had me tested.





Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: