While this is a standalone proposal, it is motivated by the go2 generics proposal (issue and doc). The connection between this proposal and generics is presented here (originally here).
In go, one can convert any type to another with the same type definition: type A int; var _ = A(int(1))
is perfectly valid (play).
This proposes an extension to this whereby slices, maps, arrays, channels and pointers with convertible inner types may also be convertible. This type of conversion is efficient as deep copies are never required (for example, for a slice only a reflect.SliceHeader is copied).
The slice conversion type A int; var _ = []A([]int{1})
is not currently valid (see FAQ), but would be under this proposal (play, uses unsafe). The same can be done with maps (play), arrays (play), channels (play), and pointers (play).
All these have in common that they may result in the same memory being addressed by mutiple pointers of different types, i.e. a *A
and a *int
may point to the same address, which is not otherwise allowed in go. While this can obviously be done via unsafe
as shown in the play links above, I put it for debate whether this can be added to the runtime safely with regards to gc, escape analysis, finalizers, etc. Note however the memory is always shared exclusively by types of equal size and which have either a common type declaration (in the traditional meaning of it) or a common 'relaxed' type declaration (in the meaning introduced by this proposal). This excludes converting []int
to []int64
, []string
to [][]byte
, etc - even though int-int64
and string-[]byte
are convertible, these are exceptions and do not have common type declarations (int64
is not type int64 int
and string
is not type string []byte
).
With regards to interfaces and structs, they follow the same proposed new rules (slices, maps, arrays, channels and pointers with interface or struct inner types may be converted to others with convertible inner types), though note the existing conversion restrictions on both interfaces and structs with regards to private methods/members still hold. Therefore type A interface {String() string}; type B interface {String() string}; var _ = []A([]B{})
would now be allowed (play), but not if A
and B
were on different packages and the String() string
method were not exported (play). Similarly for structs (play), with an equivalent restriction on private members in different packages (play). Converting from non-interface to interface (i.e. []interface{}([]int{})
) or to a non-equivalent interface (type I interface {Foo()}; []interface{}([]I{})
) is not allowed.
This proposal does not change the conversion rules for func
, i.e. type A int; var _ = (func(A))(func(int){})
is still illegal.
It might also be possible to allow conversion of structs to structs of different type definition, but differing only in the types of their members, and these being convertible in the context presented above. For example, given type A struct {I int}; type MyInt int; type B struct {I MyInt}
, we could allow B(A{})
. I am not certain this would add value under the generics proposal or indeed if it has additional technical difficulties, so I am leaving out of this proposal.
The original use of these changes is in the context of the generics proposal, discussed here (originally here). On its own, this has the advantage of limiting the need to perform deep copies unnecessarily: if an API requires a []MyInt
for type MyInt int
, a []int
can be passed in without the need of copying. This however is not normally required as it is go standard to keep APIs to standard types whenever possible, meaning the real value of this proposal is closely tied to the generic one and should be discussed primarily in that context.
Please discuss this proposal here, and whatever applies to generics in particular in golang/go#15292 (comment).