Description
I'm not sure if this is a bug or should be a proposal, or if it is just a curiosity. But I noticed a thing and I think it's weird.
Say package a
wants to make sure that of some type, only well-controlled values are ever used (in my use case, its an interface which I want to share between packages but be able to change without breaking compatibility). It uses an internal package to do so:
-- a/a.go --
package a
import "a/internal"
func MakeX() internal.X {
return internal.Make(42)
}
func F(x internal.X) {
if x.V() != 42 {
panic(fmt.Errorf("invalid %#v", x))
}
}
-- a/internal/internal.go --
package internal
type X struct { v int }
func Make(v int) X { return X{v} }
func (x X) V() int { return x.v }
-- b/b.go --
package main
import (
"a"
// illegal: use of internal package a/internal not allowed
// "a/internal"
)
func main() {
// doesn't work
// a.F(internal.X{})
a.F(a.MakeX())
}
As a/internal
cannot be imported, there is no way to get an internal.X
that isn't sanctioned by a
. reflect
can be used to construct an any
with dynamic type internal.X
, but that can't be type-asserted, so it can't be passed to a.F
.
However, using generics, we can do this trick:
package main
import "x/a"
func main() {
a.F(trick(a.MakeX))
}
func trick[T any](f func() T) T {
return *new(T)
}
The same also applies if the type is not from an internal package, but an unexported type. I suspect a similar issue would apply to #21498 (there is currently no way for a different package to write a func(unexported)
, but #21498 would allow it by spelling it (x) => { … }
).
I'm not sure how important it is to prevent this. But I don't think this is how it should work. I think if an inferred type argument is from an internal package or is a defined type with unexported name from a different package, it should fail. But doing that would technically be a breaking change.
Just thought I'd bring this up, at least.