Skip to content

proposal: Go 2: allow covariance of interface method parameters & return values #30602

@kent-h

Description

@kent-h

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:

  • Information hiding
  • Interface simplification (from the caller's point of view)

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')

type ExampleInterface interface {
  // return type covariance
  Clone() ExampleInterface
  // parameter type covariance
  SetParent(*ExampleStruct)
}

Struct (for this example, the 'supertype')

type ExampleStruct struct {
  ...
}

Implementation

// return type covariance (this implementation's return type is a supertype of the interface method's return type)
func (s *ExampleStruct) Clone() *ExampleStruct {
  ... 
}
// parameter type covariance (this implementation's parameter type is a subtype of the interface method's parameter type)
func (s *ExampleStruct) SetParent(newParent ExampleInterface){
  ...
}

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 if C implements D if and only if A 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 override Method(implementationStruct)interfaceType, because all information required by the embedded interface is there.

Other Considerations

  • This change would be GO 1 compatible.
  • What about slice/array/map types?

    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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions