-
Notifications
You must be signed in to change notification settings - Fork 17.7k
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
Proposal: allow conversion between recursively equivalent types #18030
Comments
I'm not sure how I feel about this. The recursive nature of this proposal means that we can explicitly convert between interface types that are entirely different, as in
Of course the conversion does have to be explicitly, so presumably people should avoid doing that. But the identical underlying type can be hidden beneath an arbitrary number of layers of function parameters. I can appreciate the desire for |
I had similar concerns about interfaces - the proposal's really targeted towards data (structs, etc) and not functions. Would you feel better if interfaces were excluded? |
Interfaces are just one aspect of the issue, because interfaces are just the dual of method sets.
This conversion is accepted by your proposal even though the values have different meanings and the value methods have different meanings, they just happen to have the same underlying type. |
Another case worth considering is oneof. For proto messages that use .proto
.pb.go
|
@ianlancetaylor - yes, I agree with what you're saying. I feel that there might be a semantic difference between interfaces and method sets - even though they behave the same way - but if most of the community doesn't see it that way, then there's little point introducing a distinction. Plus, as @russ points out, generated protobuf code does use interfaces, so we would have to support them. @russ, thanks for pointing this use case out, I had forgotten about it. Given the current protobuf generator, I think the options would either be to ignore the method names (and only verify compare the other aspects of their signatures), or else insist that the top-level message names had to be the same. The first option seems too lax to me - if an interface contained more than one method with the same signature (ignoring the name), it would be either impossible or very brittle to determine which method in the first type should match to the second. The second option is feasible but unfortunate, since nothing else in this proposal cares about the names of the types themselves. So this would reduce the usefulness of this proposal, and could cause working code to suddenly break if a developer added a If we can change the protobuf golang generator, we have a few additional options:
I don't find any of these options terribly appealing. My best guess would be to sacrifice |
Since we're mostly talking about generated code, why couldn't we
make the code generator help us to write the type conversion boilerplate?
I also feel this relax the type system too much and allow some potentially
dangerous conversions, like two types with different method set (and
different
invariants) but happens to be SE. I know, the user has to be explicit about
the conversion, but when writing the code, people might not remember that
the two structs have different invariants, and incorrectly assumes that if
the
compiler allows the code, it should be fine.
|
@minux - the code generator won't know the different types you want to convert to/from in the general case. However, I agree that (in theory) we could make some changes to the generator to simplify these use cases. |
As Ian pointed out in #18030 (comment), there is significant complexity lurking here. Part of the reason things are as locked down as they are is to make it OK for people to change their own types in specific ways (like adding struct fields) without fear of breaking other people's programs. The type system does not seem like something we should modify incrementally one step at a time without very good reason. We had a good, specific, compelling reason for #16085. What is the specific, compelling reason here? |
Hi @rsc , the compelling reason is being able to copy information between two identically written but distinct proto messages. However, allowing that kind of casting would break the use case you're mentioning - if one of the protos changed, the code would break. Given the complexities, perhaps this isn't worthwhile - maybe an extension to protoc that could provide translators between two specified protos would be better if there's sufficient demand. |
OK, let's decline this until there is a more compelling reason. |
Summary: I propose we consider relaxing struct conversion rules so that when converting composite data types (eg structs and slices), we recursively attempt to convert element types as well as the fundamental types. This would simplify certain important use cases, such as the handling of (otherwise) identical protobuf messages.
1. Background: This proposal is inspired by the (accepted) proposal described in #16085, and heavily references that proposal, but has a significantly wider scope. For example, under the current type conversion rules, we can convert between the following two types:
But we cannot convert between the following two types, even though they are clearly identical, from a data perspective:
Unfortunately, this is very similar to the kinds of structures that are created by the Protocol Buffer compiler. This means that many structurally identical messages cannot easily be converted from one proto package to another.
Issue #16085 (Section 5) did specify that tags should be ignored recursively, for the purposes of type conversion, but this is only relevant for anonymous structs and therefore is not widely applicable. However, it does foreshadow the issues raised by the this proposal.
2. Proposal: The current spec defines that “A non-constant value x can be converted to type T [if, among other cases,] x's type and T have identical underlying types.” Instead, I suggest that this be changed to “x’s type and T have structurally equal (SE) types.” As one might expect, two types are SE if they meet any of the current criteria (including being of the same underlying type), or if they:
To put this in concrete terms for each of Go's composite types:
In addition to changing the built-in language behaviour, we would also update the reflect package to match this change (
Type.ConvertibleTo
andValue.Convert
).3. Impact: as with #16085, this is a backwards-compatible change since it only loosens restrictions. The only exceptions to this are the behaviour of several methods from the “reflect” package, as described in #16085. Furthermore, the benefits should be greatly enhanced when dealing with protobufs, as described in the background.
4. Discussion: While the primary driver for this change was to simplify the handling of structures, I have included functions, methods and interfaces for completeness. However, if the inclusion of these functional elements causes problems, it may be acceptable to remove them from this proposal as well.
A further note should be made about method sets. Under the current rules, it is already possible to cast one type to another and completely change its method sets. As a result of the current proposal, the method sets of its element types may also change. For example, if
x.a.Foo()
was a valid call, andx
is casted toy
(a variable of a different but SE type tox
), this does not necessarily mean thaty.a.Foo()
will be a valid call - or if it is a valid call, that it will refer to a method with the same signature, let alone semantics.5. Alternatives: One alternative to allowing automatic conversion is for developers to continue to manually specify every field that needs to be converted. Another is to provide a library function, which would fail at runtime (instead of at compile time) if the two types were not SE. There would also be performance and memory considerations, as in #16085.
6. Implementation: TBD. In the compiler, we would need to generalize the type identity functions to recurse in the case of conversions. The spec would be updated with a new section on structural equality, and the reflect package would need to be updated to match the behaviour of the compiler.
Thanks to @griesemer for some preliminary comments on this proposal.
The text was updated successfully, but these errors were encountered: