Skip to content

spec: unable to create constraint based upon type parameter: "type in term ~T cannot be a type parameter" #58590

Closed
@daniel-santos

Description

@daniel-santos

What version of Go are you using (go version)?

https://go.dev/play/p/Ztyu2FJaajl
go1.20.1

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?

package main

type A[T any, U ~T] struct{}
type B[T any, U ~T | ~*T] struct{}
type C[T any] interface{ ~T | ~*T }

func main() {}

What did you expect to see?

no error

What did you see instead?

./prog.go:3:18: type in term ~T cannot be a type parameter
./prog.go:4:18: type in term ~T cannot be a type parameter
./prog.go:5:27: type in term ~T cannot be a type parameter

Go build failed.

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 embeds T, 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.

Activity

ianlancetaylor

ianlancetaylor commented on Feb 21, 2023

@ianlancetaylor
Contributor

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

changed the title [-]Unable to create constraint based upon type parameter: "type in term ~T cannot be a type parameter"[/-] [+]spec: unable to create constraint based upon type parameter: "type in term ~T cannot be a type parameter"[/+] on Feb 21, 2023
added
NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.
on Feb 21, 2023
added this to the Backlog milestone on Feb 21, 2023
griesemer

griesemer commented on Feb 22, 2023

@griesemer
Contributor

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.

added
DocumentationIssues describing a change to documentation.
and removed
NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.
on Feb 22, 2023
self-assigned this
on Feb 22, 2023
griesemer

griesemer commented on Feb 22, 2023

@griesemer
Contributor

Similarly, type A[T any, U T | *T] struct{} is not permitted because the 2nd type constraint in the type parameter list is syntactic sugar for interface{ T | *T } where T is a type parameter, and such interface elements are not permitted for now (see the rules and examples in https://tip.golang.org/ref/spec#General_interfaces).

griesemer

griesemer commented on Feb 22, 2023

@griesemer
Contributor

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.

pPanda-beta

pPanda-beta commented on Dec 2, 2023

@pPanda-beta

@griesemer

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. [T I, I any] T satisfies I, then we can prevent two things

  1. Unnecessarily slow code, any(is[i]).(I), this is runtime conversion of T to any, and then any to I.
  2. Runtime errors due to type mismatch. (the above example where string does not satisfy fmt.Stringer)
griesemer

griesemer commented on Dec 4, 2023

@griesemer
Contributor

I don't see how your example is connected to this issue.
I also don't see why your example should be workable: a string is not fmt.Stringer.

Garciat

Garciat commented on May 16, 2024

@Garciat

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
}
pPanda-beta

pPanda-beta commented on May 18, 2024

@pPanda-beta

@griesemer

I don't see how your example is connected to this issue.

Because there is not type constraint like c++/java/kotlin, only runtime casting is available today in golang.

I also don't see why your example should be workable: a string is not fmt.Stringer.

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 type IfaceSlice[T ~I, I any] []T or just type IfaceSlice[T I, I any] []T we can easily ask compiler to check ifs := IfaceSlice[string, fmt.Stringer](a) and validated that it is impossible.


In last comment @Garciat explained this exact problem in that example.

daniel-santos

daniel-santos commented on May 19, 2024

@daniel-santos
Author

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.

@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.

Garciat

Garciat commented on May 19, 2024

@Garciat

@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.)

Garciat

Garciat commented on May 19, 2024

@Garciat

The specific issue that I brought up in my code snippet actually relates more closely to #47127.

ianlancetaylor

ianlancetaylor commented on May 19, 2024

@ianlancetaylor
Contributor

@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.

locked and limited conversation to collaborators on May 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

    Development

    No branches or pull requests

      Participants

      @Garciat@daniel-santos@ianlancetaylor@griesemer@gopherbot

      Issue actions

        spec: unable to create constraint based upon type parameter: "type in term ~T cannot be a type parameter" · Issue #58590 · golang/go