-
Notifications
You must be signed in to change notification settings - Fork 17.7k
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
proposal: Go 2: a type set with a single type should act more like that type #66123
Comments
F2 is underconstrained. I don't think there is much to do here. F2 needs to be constrained properly and the rest should be left to constraint type inference. |
The type of T is irrelevant, but we know that the argument to F2 must be a pointer type (to what it points, we do not constrain.) We can say the same about the argument to In fact, go knows that type Fooer interface {
Foo()
}
type Bar struct{}
func (*Bar) Foo() {}
func F1[T any, PT interface{
Fooer
*T
}](value PT) {
F2((*T)(value))
}
func F2[T any](*T) {} This code is valid, proving that values of |
Indeed. What I meant is that F2 doesn't know about chan PT. It is seen as a type argument as a whole. In other terms, chan PT is not really seen as a composite type argument by F2. |
This is interesting. To my understanding this extra constraint could be made unnecessary as the compiler has all the information required to understand that PT will meet the constraints for F2. I should probably re-word the proposal to talk more about type covariance. The go FAQ explains, in a roundabout way in the Java generic section, why it doesn't handle covariance. I find this explanation somewhat unsatisfactory as the simplicity should be heavily weighted towards the code written in the language, not the language implementation (easy to say for me who doesn't work on the compiler 😬.) |
Hmmh, I think your initial title is a bit more accurate as I am not sure it is related to variance. Or at least, there is probably a better way to describe the issue. I think that it is mostly the treatment of composite types that is still being worked upon. |
Covariance is how one would describe a composite type which follows the same sub-super rules no? (chan T is covariant across T, in this case PT is a subclass of *T therefore a tighter constraint and should be assignable.) But agreed there's probably a better way to describe this, I just lack the knowledge :) |
I guess you're right. This seems to be some sort of type parameter covariance. Why not :) |
We don't support covariance anywhere else in Go. See https://go.dev/doc/faq#covariant_types. I would be nervous about permitting it just for type parameter satisfaction. I would want to see a very compelling argument for why it makes it possible to write code that is currently difficult to write. |
Based on the discussion above, this is a likely decline. Leaving open for four weeks for final comments. |
I think the issue is mistitled. This isn't about covariance. First, One way to demonstrate that this is definitely somehow related to generics specifically, is to check if the code under consideration works if you manually monomorphize it. Which it does. So, this isn't just a case of doing something special for generic code: There is something about it not working that's because of generics. Then the example by @jordan-bonecutter to compare with pointers seems unfortunate to me. In that example, a value of In regards to the assignment it turns out the pointer code even works without the conversion (note: I renamed the type parameters, to make the comparison to the spec easier). This is due to the assignability rules for type-parameters:
I think the last rule applies here: This demonstrates that it's important whether or not we directly use a type-parameter or not. In the top-post example, I think a closer analogon to the top-post example, involving pointers, is this one: type Fooer interface {
Foo()
}
func F1[X any, PX interface {
Fooer
*X
}](value PX) {
var p *PX // composite type involving PX
F2[X](p) // cannot use p (variable of type *PX) as **X value in argument to F2[X]
}
func F2[T any](p **T) {} This doesn't work and neither does a conversion. So I think we can minimize the example somewhat: func F[X any, PX *X]() {
var (
x []*X
p []PX
)
x = p
x = ([]*X)(p)
_ = x
} Or arguably even further: func F[X int]() {
var (
x []int
y []X
)
x = y
x = ([]int)(y)
_ = x
} I'm using slices here, arbitrarily - it could be any composite type. So the core complaint about this issue, AFAICT, is: If I have two composite types T1 and T2, where T2 involves a type-parameter that has exactly one type in its type set if I replaced that type parameter with the only type in its type set, T2 and T1 would be identical - should they then be assignable and/or convertible to each other? I think this is losely related to @griesemer's #63940 as well, in the vague sense that this is about "well, the code is equivalent with the only type in the type set, so it really should be treated the same", which is what core types kinda do. Perhaps. @ianlancetaylor I would make the case that while this is currently working as intended, there is something here, despite the incorrect attribution of the problem. |
@Merovius Thanks for looking into this. |
Undoing likely decline status. This needs further thought. Thanks. |
Paradoxically, I think that the initial title is fine too although this is not necessarily the common nomenclature. This can be considered some sort of variance issue but restricted to type parameters (hence the "generic") but that's not too important. I think PT is somehow in the type set that represents all pointer types and that info somehow gets lost once we deal with chan(PT) (compound types i.e. Types which don't have a core type will share that issue as was demonstrated above). That's why chan(PT) can't be passed to F2 while a real instantiation doesn't error out. That might solve itself once the core type issue that was linked is processed. |
This should be covered by removing the core type concept, which is #63940. So, closing this issue as a dup of that one. Thanks. |
Proposal Details
Go Programming Experience
Experienced
Other Languages Experience
JS, C, Python
Related Idea
Has this idea, or one like it, been proposed before?
No, not to my searching
Does this affect error handling?
No
Is this about generics?
Yes. This proposal allows adding methods on a subset of generic types.
Proposal
Consider the following code:
Go complains with the following error:
While this is not false (
ch
is of typechan PT
),PT
is a*T
, according to our constraints. Therefore any generic typeType[PT]
will be equal toType[*T]
.Formally, if any of the type parameters in a type constraint are concrete (non-interface) types, then any values adhering to that constraint may be considered as an instance of that concrete type.
The current workaround is to cast the value to any, and then downcast to the concrete type which is unnecessary and not intuitive.
Language Spec Changes
No changes to syntax, only recognizing that such a downcast is valid.
Informal Change
No response
Is this change backward compatible?
Yes, only more go programs are considered valid.
Orthogonality: How does this change interact or overlap with existing features?
The goal of this change is to improve the usability of generic utility types. Its success should be measured by its usage.
Would this change make Go easier or harder to learn, and why?
This doesn't increase the complexity of the language from a usability perspective.
Cost Description
Not every instance of a generic type will have the same method set, and the compiler will need to check this.
Changes to Go ToolChain
No response
Performance Costs
Compile time costs: negligible if not used. Run time cost: better than interface smuggling.
Prototype
No response
The text was updated successfully, but these errors were encountered: