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

doc: document idiomatic way to ensure that type satisfies a generic constraint #50813

Open
virtuald opened this issue Jan 25, 2022 · 9 comments
Open
Labels
Documentation NeedsInvestigation
Milestone

Comments

@virtuald
Copy link

@virtuald virtuald commented Jan 25, 2022

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

// This playground uses a development build of Go:
// devel go1.18-16d6a5233a Tue Jan 25 00:39:08 2022 +0000

What did you do?

https://gotipplay.golang.org/p/RX-7wE7yCzp

package main

// traditional golang interface
type I interface {
	Fn() int
}

// generic interface
type TI[T any] interface {
	Fn() T
}

// generic constraint
type CI[T any] interface {
	~struct {
		Thing int
	}
	Fn() T
}

//
// Implementation
//

type Concrete struct {
	Thing int
}

func (c Concrete) Fn() int {
	return c.Thing
}

// ok
var _ I = Concrete{}

// ok
var _ TI[int] = Concrete{}

// error: interface contains type constraints
var _ CI[int] = Concrete{}

// this doesn't seem very idiomatic
func SatisfiesCI[C CI[int]](c C) {}

func init() {
	SatisfiesCI(Concrete{})
}

Description

The current FAQ describes the idiomatic way to ensure your type satisfies an interface. I find I use this idiom a lot to make sure I don't accidentally break concrete implementations of things that are only used by a few consumers of a library, but not necessarily in the library itself (... or maybe I'm too lazy to write a test for it :shipit:).

As I was migrating a project to see what it would look like under generics, I made some of the interfaces generic and that worked fine. Unfortunately, this sort of check doesn't work with constraints, and there doesn't seem to be a good way to ensure that your type satisfies the constraint other than creating a function to do so.

I get why you can't assign to a constraint interface -- you can't create variables that "are a constraint".

It would be good for someone more experienced with generics to decide on an idiomatic way to do this sort of 'constraint assertion', and make sure it ends up in the FAQ. It's possible there isn't a non-clunky way right now, and that this issue would need to be deferred until more experience is had with generics.

It's also possible I missed an earlier issue/discussion that asked for this, in which case I apologize (but I did look!).

@ydnar
Copy link

@ydnar ydnar commented Jan 26, 2022

Would this work?

https://gotipplay.golang.org/p/EKwVbjuZYEK

var _ any = TI[int](Concrete{})

@virtuald
Copy link
Author

@virtuald virtuald commented Jan 26, 2022

No, that's for TI, which is just a generic interface. The constraint is CI.

@mknyszek mknyszek changed the title faq: document idiomatic way to ensure that type satisfies a generic constraint doc: document idiomatic way to ensure that type satisfies a generic constraint Jan 26, 2022
@mknyszek mknyszek added the NeedsInvestigation label Jan 26, 2022
@mknyszek mknyszek added this to the Go1.19 milestone Jan 26, 2022
@mknyszek
Copy link
Contributor

@mknyszek mknyszek commented Jan 26, 2022

CC @ianlancetaylor maybe?

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jan 26, 2022

The best I can come up with is

func SatisfiesCI[_ CI[int]]() {}
var _ = SatisfiesCI[Concrete]

Anybody have any other ideas?

@ydnar
Copy link

@ydnar ydnar commented Jan 26, 2022

This?

https://gotipplay.golang.org/p/lq3GDh15HpB

type _ interface {
	CI[int]
	Concrete
}

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jan 26, 2022

@ydnar I don't see how that ensures that Concrete satisfies the constraint CI[int]. That will work if you substitute any other type for Concrete.

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jan 26, 2022

This version does it in a single declaration, but you still have to give the declaration a real name:

type SatisfiesCI[_ CI[int]] struct {
	_ *SatisfiesCI[Concrete]
}

@virtuald
Copy link
Author

@virtuald virtuald commented Jan 26, 2022

One of my first tries at this tried to define a variable with an anonymous struct and assign it, but I quickly discovered you can't add a type parameter to an anonymous struct so that didn't work... and it's a lot of punctuation.

// this doesn't work
var _ = struct[x CI[int]{_ x}{Concrete{}}

ianlancetaylor's single definition is better than the functional definition.. but it's not terribly obvious what it's doing (though maybe that's why it should be named?). Though, I guess is the standard idiom really all that obvious if you haven't seen it before either?

One could add a general 'Satisfies' function to the constraints package? But I don't believe there's a way to make a function that would work for all types/constraints without some C++-style meta-madness. But if there were, then you could write something like:

var _ constraints.Satisfies[CI[int], Concrete]

@virtuald
Copy link
Author

@virtuald virtuald commented Jan 30, 2022

func SatisfiesCI[_ CI[int]]() {}
var _ = SatisfiesCI[Concrete]

I've been using this one from Ian with a specific flavor. I create the SatisfiesXXX function right next to the constraint, so it's really obvious why it exists even with complex generic parameters. It gives a nice error message, and in particular is really easy to understand at the point the error is generated because it's just two names, similar to the original idiom. Also, at the point of use, type inference makes it so you often (but not always) only need to put in the typename, regardless of the number of generic parameters.

type SatisfiesCI[_ CI[int]] struct {
  _ *SatisfiesCI[Concrete]
}

I found that the single-declaration version that Ian came up with is really difficult to write correctly, really difficult to read, and particularly if there are a few generic parameters you're doing a lot of duplication of text.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Documentation NeedsInvestigation
Projects
None yet
Development

No branches or pull requests

5 participants