Skip to content
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

cmd/compile: seemingly valid generic interface rejected #68162

Open
griesemer opened this issue Jun 24, 2024 · 10 comments
Open

cmd/compile: seemingly valid generic interface rejected #68162

griesemer opened this issue Jun 24, 2024 · 10 comments
Assignees
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@griesemer
Copy link
Contributor

griesemer commented Jun 24, 2024

I don't see a good reason why the following interface should be invalid:

type Element[E Element[E]] interface {
	Less(E) bool
}

Yet the compiler reports an invalid recursive type.
See example application here.

@griesemer griesemer added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Jun 24, 2024
@griesemer griesemer added this to the Go1.24 milestone Jun 24, 2024
@gopherbot gopherbot added the compiler/runtime Issues related to the Go compiler and/or runtime. label Jun 24, 2024
@gabyhelp
Copy link

@magical
Copy link
Contributor

magical commented Jun 24, 2024

Your playground example asserts that it is not possible to factor

type SortedList[E interface{ Less(E) bool }] []E

into

type SortedList[E Element[E]] []E
type Element[E] ...something...

But this definition seems to fit the bill:

type Element[E any] interface {
	Less(E) bool
}

https://go.dev/play/p/nqgWVreqDoW?v=gotip

Maybe i'm missing something but i'm not sure why Element's type argument would need to itself be constrained by Element. That does seem troublesomely recursive.

@magical
Copy link
Contributor

magical commented Jun 24, 2024

If Go had a Self type, you could write something like this and avoid needing to pass E to Element as an argument, but of course it doesn't.

type SortedList[E Element] []E

type Element interface {
    Less(Self) bool
}

@timothy-king
Copy link
Contributor

timothy-king commented Jun 24, 2024

I think the most relevant comparison is with something that does work already, which is not naming the constraint:

type I[E interface{ Less(E) bool }] interface {
	Less(E) bool
}

@zephyrtronium
Copy link
Contributor

I believe this is a duplicate of #63149.

@ruyi789
Copy link

ruyi789 commented Jun 25, 2024

Firstly, you can't create yourself, and secondly, you don't exist yet.

@arvidfm
Copy link

arvidfm commented Jun 25, 2024

This kind of recursive constraint is needed to be able to express interfaces that reference types that in turn have type parameters with the same constraint. For instance:

type ElementList[E Element[E]] []E

func (el ElementList[_]) IsSorted() bool {
    for i := 0; i < len(el) - 1; i++ {
        // E must be Element to be able to call Less()
        if !el[i].Less(el[i+1]) {
            return false
        }
    }
    return true
}

type Element[E Element[E]] interface {
    Less(E) bool
    Children() ElementList[E] // E must be Element
}

The limitation discussed in this issue currently makes it impossible to define an interface like this.

I've run into this limitation multiple times, in particular in the context of self-descriptive types, i.e. types that model themselves - for example, a type that holds data from a database and also has methods that describe what fields correspond to what database columns, how the type relates to other types, etc. That often leads to this kind of recursive constraint and has resulted in a lot of refactoring to work around, generally having to make tradeoffs in regards to how ergonomic the API is, requiring more boilerplate or auxiliary types for the API user.

@griesemer
Copy link
Contributor Author

@magical Thanks for pointing this out. I missed that while hastily writing this up so as not to forget about it before leaving for home. That said, there may still be situations where the circularity might be needed (see @arvidfm's example); and more generally there's a question as to whether there's a more principal reason why such declarations should or should not be valid.

@zephyrtronium Thanks for finding the duplicate #63149. Leaving both open for now to capture both discussions.

@arvidfm Just to be clear, the only thing that doesn't work in your example is the Children definition. The rest of the code will work with Element[E any] instead of Element[E Element[E]] (playground).

@magical
Copy link
Contributor

magical commented Jun 25, 2024

@griesemer Indeed. Thank you @arvidfm for the example. I can see how mutually-recursive types would cause more difficulties. The self-recursive case seems much less compelling on its own since it seems easy to eliminate the recursion by hand.

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/605755 mentions this issue: go/types, types2: permit type cycles through type parameter lists

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
Development

No branches or pull requests

8 participants