On Fri, Mar 12, 2021 at 10:16 AM Brian Candler <b.cand...@pobox.com> wrote:

> I can see two separate things under discussion - compatibility of struct
> types for conversion, and backwards-compatibility of serialization [JSON?
> Protobuf?]
>

Backwards-compatibility of serialization is a red herring. It is off-topic
as far as this thread is concerned, this is purely about language-level
compatibility of types.


> but I can't see what's being proposed to change.
>

I think the proposed change can be summarized as roughly

Either
> 1. Create consensus that adding an (exported) field to a struct should be
> considered a backwards incompatible change, or
> 2. Create consensus that converting one struct type into a different
> struct type should be considered deprecated, as it makes you vulnerable to
> getting broken by someone adding a field.
> Take either consensus and put it into the Go 1 compatibility promise and
> potentially encode it into a vet check.


That is my understanding of what's "proposed". Personally, as I said, I'm
not in favor of either. I think both statements lack nuance.

The logical conclusions of the arguments brought forth is that every
publicly visible change is a breaking change. So if we follow that logic
we'd end up (in my opinion) in an ecosystem where every module is
constantly incrementing their major versions because "we technically broke
the API". I think that is undesirable.

Given that every publicly visible change to an exported identifier can be
considered a breaking change, I personally don't think it's all that useful
to talk about interface-changes vs. struct changes and which of them are
breaking. Both are - in some circumstances. And in some circumstance,
neither are. It's more useful to talk about *how frequently* any given
change leads to breakages. I think it's easy to make an argument that
changing in interface type will lead to very frequent breakages - it will
either break every implementation of the interface, or any usage of the
interface, or both.

Meanwhile, I think it's also not that hard to make an argument that
breakages causing by converting one struct type into another and then
adding a field to one of them are *comparatively* rare. And even within
that, there is nuance, where it is more frequent for some kind of types
(POD - plain-old-data) than others (e.g. http.Server or other types used to
abstract and bundle state).

That's why I don't think a vet check would work. It can't distinguish the
case of a struct being POD vs. the struct bundling state. And I would even
argue that generally, the "bundling state" case is more common in Go.


> I don't follow the argument that "adding a field to a struct is *now* a
> backwards incompatible change".  Surely this was always the case,
> regardless of tags?  That is, if you have
>
> type T1 struct {
>     Foo string
> }
>
> type T2 struct {
>     Foo string
>     Bar string
> }
>
> then what would you expect to happen for:
>
>     v1 := T1{}
>     v2 := T2(v1)   ??
> ...
>     v2 := T2{}
>     v1 := T1(v2)  ??
>
> These two structures have different shapes.  In the first case, I suppose
> it could copy the matching members and create zero values for the new
> ones.  In the second case, I suppose it could copy the matching members and
> silently(!) discard the missing ones.  But either way, that just means the
> compiler magically writing code which copies member-by-member.  I'd rather
> do this by hand.
>
> Even with magic conversions, trying to use a *t1 as a *t2 or vice versa
> would definitely not work, as they have differing layout in memory. Indeed,
> even the ordering of fields in memory must be the same for two structs to
> be compatible:
> https://play.golang.org/p/BTUc6mNJQKS
>
> However, if two structures have the same members in the same order, and
> differ only by tags, then they have the same shape.  Then it seems
> reasonable to me to treat a T1 as compatible with T2; copying can be done
> as a blob of bytes.  The change in 1.8 to omit comparing tags has made
> types more compatible, not less.
>
> Separately from that, there's the issue of serialization and
> forwards/backwards compatibility.  I don't see any problem here.  For
> example, if you serialize a type T1 to JSON, and then deserialize to T2
> (which may have more or fewer fields), that all works fine.
>
> https://play.golang.org/p/aJwObgyRhan
>
> If you want to reject unexpected fields when deserializing, there are
> options for doing that.  But by default, they are compatible in both
> directions.  You can also re-order the fields in the struct, since the JSON
> deserialization is assigning elements individually.
>
> --
> You received this message because you are subscribed to the Google Groups
> "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to golang-nuts+unsubscr...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/golang-nuts/36e1a048-2505-4841-8882-a9b16a33fd57n%40googlegroups.com
> <https://groups.google.com/d/msgid/golang-nuts/36e1a048-2505-4841-8882-a9b16a33fd57n%40googlegroups.com?utm_medium=email&utm_source=footer>
> .
>

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/CAEkBMfE09%2BW0MU55LeJfm-9BNM_7SZhGkfRxD4wkP4Rj53Eq_A%40mail.gmail.com.

Reply via email to