-
Notifications
You must be signed in to change notification settings - Fork 17.8k
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
spec: unable to create constraint based upon type parameter: "type in term ~T cannot be a type parameter" #58590
Comments
In https://go.dev/ref/spec#Interface_types the spec says "In a term of the form ~T, the underlying type of T must be itself, and T cannot be an interface." Perhaps it should also say that T can't be a type parameter. CC @griesemer |
The underlying type of a type parameter is an interface, namely the constraint interface (see https://golang.org/ref/spec#Underlying_types). So I think what the spec says is accurate. But the fact that the underlying type of a type parameter is an interface is an esoteric aspect of the type system/spec and perhaps needs to be rethought or communicated better. Re: the numbering of spec sections: it's not a bad idea but requires some tooling so that the spec's numbering and compiler errors don't get out of sync. That said, as of Go 1.20 the compiler internally uses unique IDs for all errors, but they are not yet communicated externally. Once they are, it will be easier to map an error to an explanation and the spec. |
Similarly, |
I'm going to close this because it appears that the spec actually has examples illustrating and explaining that the code alluded to in this issue is not permitted. |
I think we should re-consider this issue and figure out some way to enable type constraints. Type constraints are important to write compile time type safe programs. Consider the following utility type Collection[T any] interface {
At(int) T
Len() int
}
// IfaceSlice[T,I] Saves memory by not creating interface objects in a slice (e.g. []I) which costs 8bytes+ extra mem than regular slice (i.e. []T).
type IfaceSlice[T, I any] []T
func (is IfaceSlice[T, I]) At(i int) I { return any(is[i]).(I) }
func (is IfaceSlice[T, I]) Len() int { return len(is) } Now the following code works. a := []*bytes.Buffer{bytes.NewBufferString("abc"), bytes.NewBufferString("def")}
ifs := IfaceSlice[*bytes.Buffer, fmt.Stringer](a)
var c Collection[fmt.Stringer] = ifs
fmt.Println(c.At(0))
fmt.Println(c.At(1)) But this one fails due to no compile time type safety. // Following is incorrect and causes runtime error.
a := []string{"abc", "def"}
ifs := IfaceSlice[string, fmt.Stringer](a)
var c Collection[fmt.Stringer] = ifs
fmt.Println(c.At(0))
fmt.Println(c.At(1)) Full example: https://go.dev/play/p/SMnrbnv-q0k Hence if we can somehow make type constraints, i.e.
|
I don't see how your example is connected to this issue. |
If we could use type parameters as constraints, we could leverage 'type relation witnesses' to work around the current limitation that methods may not be generic (or add constraints). type TyRel[T any, U any] interface {
Apply(T) U
ApplyAll([]T) []U
}
type TyEq[T any] struct{}
func (_ TyEq[T]) Apply(x T) T { return x }
func (_ TyEq[T]) ApplyAll(xs []T) []T { return xs } // little optimization for some cases
func Refl[T any]() TyRel[T, T] { return TyEq[T]{} }
type Implements[T U, U any] struct{} // can't do this
func (_ Implements[T, U]) Apply(x T) U { return x }
func (_ Implements[T, U]) ApplyAll(xs []T) []U { .. Apply to each element .. } // not much use, really
func Impl[T U, U any]() TyRel[T, U] { return Implements[T, U]{} } import "fmt"
import "strings"
type Slice[T any] []T
// TyRel[T, string] is a witness that T~string
func (s Slice[T]) JoinStrings(rel TyRel[T, string], sep string) string {
return strings.Join(rel.ApplyAll(s), sep)
}
// TyRel[T, fmt.Stringer] is a witness that T <: fmt.Stringer
func (s Slice[T]) JoinStringers(rel TyRel[T, fmt.Stringer], sep string) string {
ss := make(Slice[string], len(s))
for i, x := range s {
ss[i] = rel.Apply(x).String()
}
return ss.JoinStrings(Refl(), sep) // missing type inference on return type for Refl() here
} type X struct{}
func (_ X) String() string {
return "hi"
}
func hello() {
xs := Slice[X]{{},{},{}}
xs.JoinStringers(Impl(), ", ") // missing type inference on return type for Impl() here
} |
Because there is not type constraint like c++/java/kotlin, only runtime casting is available today in golang.
Correct and that was my point. I had mentioned "But this one fails due to no compile time type safety." means go compiler should tell me that "string is not fmt.Stringer", not go runtime. In short we want In last comment @Garciat explained this exact problem in that example. |
@griesemer, the bug title starts with "spec:" and while I could be mistaken, I believe that is what I originally entered as the title (I won't swear to it). So to close using the reasoning that the compiler behaves per-spec seems to be in bad faith. Please correct me if I'm missing something. I've been programming for 40 years now (yikes, I'm getting old!) and I know a little bit about languages, compiling, intermediate representations, etc. (the last two probably not as well as @ianlancetaylor), and I say that the specification needs some serious work to make this a "real" language feature and not just something munged on to the side. It could be something very powerful, but right now it isn't. Kindly re-open this bug report. PS: The worst time to find out that your code fails due to the lack of type safety is when it occurs in a seldom-used branch of your code. This isn't acceptable for any important program, so it sort-of rules out the use of templates there. |
@daniel-santos: I think @griesemer's POV is that the issue raised seems to be a bug report against the spec; and the spec does mention this use of type parameters. So there's nothing to be done here. Perhaps we should frame this as a feature request against the spec. (And do so in a new issue; as to 'reset' the context of the conversation.) |
The specific issue that I brought up in my code snippet actually relates more closely to #47127. |
@daniel-santos Are you saying that the spec and the compiler disagree, or are you saying that the language should change? I thought you were saying the former, in which case it is not bad faith to close the issue saying that in fact the spec and the compiler do agree. If you are saying that the spec, and the language, should change, then you should file a language change proposal. Thanks. |
What version of Go are you using (
go version
)?Does this issue reproduce with the latest release?
Yes
What operating system and processor architecture are you using (
go env
)?Go Playground server
What did you do?
What did you expect to see?
no error
What did you see instead?
Too bad Google isn't using numbered sections in the Go spec, that would make it easier to cite precise specification items.
So constraints are interfaces and implicitly converted to them using the syntax of the first four examples. The spec states that "An interface type
T
may not embed a type element that is, contains, or embedsT
, directly or indirectly." However, we're specifying a type with T as an underlying type. If I read the specification correctly then this should be legal.It's probably a separate issue with the specification that a constraint of
type A[T any, U T | *T] struct{}
is not legal.The text was updated successfully, but these errors were encountered: