-
Notifications
You must be signed in to change notification settings - Fork 18k
proposal: Go 2: allow covariance of interface method parameters & return values #30602
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
Comments
See also #21651. |
The two items under "Implementation Details" sound like big problems that would need to be solved. I also don't actually understand how to implement this. Code can use type assertions to convert from one interface type to another. Under this proposal, that would mean that we can convert from an interface with a method that returns one type to an interface with a method that returns a different type. The different types can have different memory representations. Where is the code that converts between those representations? |
Have I misunderstood something? Edit: Unboxing is never required, since a struct type is always a supertype of an implemented interface, and due to the proposal ensuring this relationship's directionality. |
When I mentioned a type assertion, I meant that I can write var v interface{} = x
w := v.(ExampleInterface) In the general case, each statement can be in a different package, and the final type assertion can be in a package that does not import the implementation package. In a case like that, all type information is not known at compile time. |
@ianlancetaylor var v ExampleInterface = x
var w interface{}
w = v // no assertion is required, since `ExampleInterface` implements (is a supertype of) `interface{}` This is the core assumption that ensures type safety. |
You omitted the final type assertion, in a different package: x := w.(ExampleInterface) |
@ianlancetaylor Since it is possible for a value of one type to be assigned to an interface of another type, it becomes possible for the interface's method signature to differ from the implementation's method signature (thus having different memory layouts).
Your issue pertains to how the implementation would handle the conversion between these formats. Have I described the issue satisfactorily? |
Sounds about right, yes. It's important to understand that the compiler need never see all the relevant types in the same package, so there is no obvious way that the compiler can handle the situation. |
@ianlancetaylor When calling any interface method
When receiving a call from an interface method
A nearly identical technique (in reverse) would be used for return values. Does this address the problem, as you see it? Am I still missing something? (I can think of several ways to optimize this, but to keep it simple for now...) |
In Go any type can have a method, so any type can be converted to an interface, so you would have to box all arguments when calling a method of an interface value. I guess it may be doable but it sounds like a very high cost. Boxing an argument in general requires a memory allocation. Calling methods of interface values like |
The idea is to simply "include type information in the method call". Optimization was intentionally skipped, for clarity. To avoid the overhead of unnecessary boxing/unboxing: For the case of passing struct -> struct (or other boxable type):
For the case of passing struct(or other boxable type) -> interface:
For the case of passing interface -> interface:
Note that the case of interface -> struct (or other boxable type) is not permitted by the proposal. To summarize the total overhead: (only incurred when calling interface methods)
That's it. No extra boxing or memory allocations required. To address your example: Ordinary usage of |
What do you mean by a static type variable? How would that work if there are multiple goroutines making simultaneous calls? I don't understand what you mean by "struct (or other boxable types)". All types are boxable. Do you just mean a non-interface type? |
By "static type variable" I just mean that the caller can determine the type at compile time. Yes, I just mean a non-interface type. (Structs are the example in my head.) |
So you are suggesting that we change the calling convention so that for every method parameter we pass an additional parameter, which is the interface type of the argument? How do we handle result parameters? |
Keep in mind that this calling convention would only be required for interface method calls. (Making the assumption that there is hidden code between the interface and the implementation, that handles the translation between these two conventions.) Result parameters would follow the exact same convention, but in reverse. |
The implementation cost of passing an additional type parameter for every potentially-covariant parameter in a method call is high. The complexity cost in the language--requiring all Go programmers to understand the use of covariance when converting a non-interface type to an interface type--is also high. We are not going to do this. |
Proposal / Rational
One of the uses for interfaces, is to provide a reduced-access abstraction, ensuring that a library is used correctly by a caller.
It is not uncommon to have similar interfaces for different classes of users; or, more generally, to have a single implementation for multiple interfaces.
This allows:
It would be useful to implement a method once, and allow it to implement multiple similar interfaces.
Simple Example
Interface (for this example, the 'subtype')
Struct (for this example, the 'supertype')
Implementation
In this example, a struct and an interface are used as the supertype and subtype, but it should be equivalently valid for both the supertype and subtype to be interfaces.
Advanced Example
See Covariance of Interface Method Parameters & Return Values - Advanced Example
Implementation Details
At compile-time, it must be determined which interfaces are sub-interface of other interfaces. (or more precisely, which interfaces are not super-interfaces of other interfaces)
This is non-trivial. As go's syntax does not specify the subtype/supertype hierarchy, loops are possible.
(i.e. -
A implements B
if and only ifC implements D
if and only ifA implements B
...)Syntactic sugar:
Allow super-interfaces to override embedded interfaces' methods, so long as the super-method is covariantly reducible to the embedded interface's method.
As an example:
Method(interfaceType)implementationStruct
could overrideMethod(implementationStruct)interfaceType
, because all information required by the embedded interface is there.Other Considerations
Is it possible to accept
[]implementationStruct
/[]*implementationStruct
in lieu of[]interfaceType
?It would be helpful to have automatic conversion of arrays/slices/maps of supertypes to arrays/slices/maps of subtypes.
This appears to be related, but may or may not be possible, and is ultimately a separate issue.
See proposal [WIP]: interface-slices #30391
Related
#28254
#8082
#8691
The text was updated successfully, but these errors were encountered: