-
Notifications
You must be signed in to change notification settings - Fork 17.6k
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: Const generics #65555
Comments
CC @griesemer |
Thanks, this is an interesting idea. I'm concerned that using |
Some grammar references in other languages: template<class T, int L>
struct vec {
std::array<T, L> arr;
...
}; RUST: fn foo<const N: usize>(arr: [i32; N]) {
// Used as a type within a function body.
let x: [i32; N];
// Used as an expression.
println!("{}", N * 2);
} Go and rust have similar type syntax structures, both of which are type-postfix syntax. So I think another possible syntax is to add the const keyword in front of the type parameter. func anyLenArr[E any, const Size ~int, A ~[Size]E]() A {}
// const mark Size is value, not a type.
// Size can be any type that underlying is int or other core types.
// usage:
func x() {
a := anyLenArr[int, 10]()
then a is [10]int
} |
One potential way of expressing other numerical constraints could be to make As far as I'm aware, there is no explicit numerical type for array lengths. The spec says:
so having the lone Another possibility for creating other types of numerical constraints, could be to set some explicit bounds on the constraint, e.g. using the familiar slicing notation. In the current proposal, indexing a generic array by a constant is not allowed, since the code should be valid for all instantiations. E.g. func first[N const](arr [N]int) {
return arr[0] // compile-time error
} would not compile, since func first[N const[1:]](arr [N]int) {
return arr[0] // allowed and safe (no runtime panics can occur)
} Explicit lower and upper bounds could also be used to loosen some of the restrictions imposed by this proposal, namely of using constant expressions that involve We can of course combine the two concepts, and define the constraint that can be used for any array lengths as the spec defines it: func foo[N const[int][0:]](arr [N]int) {
// some code
} But since Of course whether or not we'd like to introduce such elaborate numerical type constraints is a topic for a future discussion, but I believe introducing a simple
Do I understand correctly, that by "one type parameter must be assignable to another" we mean that one must implement the other? I believe this could be expressed by making one type parameter the constraint of the other (which is not currently allowed in Go). func foo[B any, A B](a A) {
var myVar B = a
// some more code
}
Is there a list I can check out of things we might want to express using type parameter constraints in the future? I would be happy to have a look and consider how this might affect the design of |
No, I mean something like func ConvertSlice[T1, T2 any](s []T1) []T2 {
r := make([]T2, len(s))
for i, v := range s {
r[i] = T2(v)
}
return r
} This could be used, for example, to convert a We can't write this today because we have no way to say that we can convert a value of type To be clear, I don't think that this issue is a good place to discuss how to handle this case. I'm only mentioning that we may not want to limit our future handling of constraints by reserving |
This particular proposal focuses on Go arrays as the motivation, but I want to present two concrete use cases for the concept of parameterized constants:
In both of my use cases, the constant is typed to |
If I am not misunderstanding, is the goal to be able to parameterize an instantiation by a (const) value instead of a type? In which case, the more general handling could also be to consider a value as a subtype (where the set of values for this type is a singleton). type Un int {1}
type someints int { 1, 2, 3} That would also deal with questions about enums especially if we have generalized interfaces/unions. (enums would not be a specific construct but simply stem from subtyping + unions, for the kind of subtypes that would be allowed to be written in Go). In a sense, that should be a form of dependent typing but more limited, practical and tractable. |
Syntax for general
|
Ahhh this const... I'd hope for const to not be necessary. The thing is that const is mainly a modifier token for an identifier. It doesn't really modify the type but rather constrains the identifier. A way to see it is that it constrains a variable to not be reassignable, neither in totality nor in parts (via interior mutability). The difficulty might reside in the handling of interior aliasing/mutability in order to ensure value equality. Probably doable in conservative ways. |
From the discussion it seems that while this proposal would be useful, there are several other uses of constant type arguments that would also be useful. If we do anything in this area we should aim for something that solves several problems rather than just this one. Therefore, this is a likely decline. Leaving open for four weeks for final comments. Thanks. |
@ianlancetaylor I believe I have addressed how we can generalise const generics in my comment above based on the discussion. Please let me know if there is anything I have missed out, and I would be happy to give it some thought. I agree that we should aim to solve several problems, rather than just this one, as such, I am happy to adapt my initial proposal as many times as necessary. |
Thanks, but I think we should restart with a consistent proposal that covers all the important cases, rather than a list of options. |
No change in consensus. |
If anyone is interested, I've made available a prototype (interpreter) implementation along with a more detailed theoretical discussion (in the style of Featherweight Go) at https://github.com/dawidl022/go-const-generics. |
Introduction
#44253 proposes to introduce generic parameterization of array sizes. The issue has been open for 3 years, with seemingly no recent progress.
The new proposal differs in that it introduces an explicit numerical
const
type parameter, and restricts the kinds of expressions that can be used to instantiate aconst
type parameter to existing constant expressions evaluating to non-negative integers, and loneconst
type parameters, in the same fashion as Rust.Having this restriction in place limits the problems const generics can solve, but it also eliminates a lot of complexity associated with implementing const generics, which were discussed in the issue of the previous proposal.
If the community is happy to proceed with this proposal, I would like to have a go at implementing the feature. As such, any concerns regarding potential implementation difficulties would be appreciated!
Proposal
The basis of this proposal is to introduce a new set of types that can be used as type arguments. This new type set would be made up of constant non-negative integers. Since interfaces represent type sets in Go, we can (conceptually) define such a type as:
where the
...
denotes that the pattern continues for all non-negative integers supported by Go. Theconst
interface can be used to constrain a type parameter, and since it is a non-basic interface it cannot be used as the type of a variable (we can already useconst
declarations to hold values, but they are broader than this proposed interface).Array types accept two "type" parameters - the length and the element type. The constraint of the length parameter is exactly the
const
interface. This proposal generalises theconst
interface to be applicable to user-defined type parameters - those we know from generics.Since all type parameters also implement their own constraints (i.e. a type parameter constrained by
T
can be used as a type argument constrained by the same type, or a supertype ofT
), it means thatconst
type parameters can be used to instantiate otherconst
type parameters, including array lengths.Expressions involving type parameters, other than a lone
const
type parameterN
, cannot be used asconst
type arguments. This approach is used by Rust and can make the implementation much more feasible. E.g. the following would not be allowed:In addition, despite
const
type parameters implementingconst
themselves, when used as a value, as opposed to a type parameter, they would evaluate to a non-constantint
. E.g. the following would not be allowed:This is to avoid
n
being used as part of another constant expression that is then used as aconst
type argument, in effect enforcing the previous limitation of no "no complex generic expressions in const arguments".The above restriction also resolves the issue that was heavily discussed in #44253 (comment) of exploiting
const
type parameters to perform complex compile-time computation, such as SAT solving.Expressions that are formed from a
const
type parameter, would follow the same rule, i.e. evaluate to a non-constant value, even if their non-generic counterparts would be computable at compile time. E.g.len
of a generic array would evaluate to a non-constant integer.The
const
interface would be a predeclared type.Examples
One of the simplest useful examples is declaring a square matrix type:
which can then be instantiated as follows:
Of course,
const
type parameters can also be used directly in functions:Who does this proposal help, and why?
This proposal is aimed at experienced Go users, who have justified use cases for using arrays over slices, and would enjoy the benefits of reduced code duplication with const generics, or would like to expose some generic functionality using underlying arrays as part of a library.
Language Spec Changes
Array types would also accept
TypeName
s, which (in my understanding) type parameters fall under.Types would also include constant expressions, more precisely constant expressions that evaluate to non-negative integers. They would be used to instantiate
const
type parameters.Type constraints would accept the keyword
const
to denote the interface containing all non-negative integer types.Other considerations
Is this change backward compatible?
Yes, this change is backwards compatible. It is not possible to name a regular interface the keyword
const
, so no existing code would break.Orthogonality: How does this change interact or overlap with existing features?
Explicit numerical type parameters would make generic arrays a first-class feature of Go, consistent with the rest of the language. All the existing compound data types in Go can already be fully type parameterised (slices:
[]T
, maps:map[K]V
and channels:chan T
), except for arrays, so this feature would bridge that gap with[N]T
.Numerical type parameters would also become first class. Currently, arrays are the only types that accept a numerical type parameter, to parameterize the length of an array type. The
const
interface would allow any type or function to accept a constant integer (or anotherconst
-bounded type parameter) as a type argument.Would this change make Go easier or harder to learn, and why?
Seeing that this is an additional language feature, it would make Go more difficult to learn simply because there is one feature more. The rest of the language will be unaffected in terms of easiness of learning.
Cost Description
The cost should be approximately the same as the existing cost of compiling generic code.
This depends on how the feature will be implemented. Using a full monomorphisation model, there would be no runtime costs over using non-generic arrays. Using the GC shape stencilling approach would likely have the same runtime cost as existing generic code.
Prototype
I am working on a paper in the style of Featherweight Go, that formalises arrays and numerical type parameters that would be implemented as part of this proposal. It will come with prototype interpreters (with static type checking) for the new language feature in the style of https://github.com/rhu1/fgg (i.e. for a subset of Go). I hope to make this publicly available, sometime post-May this year.
Has this idea, or one like it, been proposed before?
In this new proposal, the
const
type parameter is explicit, which is consistent with the existing generics system in Go, and makes it immediately obvious to the programmer when a type is parameterised on an integer value.In addition, this new proposal places restrictions on how
const
type parameters can be used, in particular when used as values they evaluate to non-constant integers, and cannot be part of a more "complex" expression used as a type argument. This means that the example presented in the previous proposal's design document cannot be implemented using const generics as presented in this new proposal. The feasibility of lifting some of the restrictions to make constructs as in the example possible is a topic for a potential future language extension.Go Programming Experience
Experienced
Other Languages Experience
Rust, Kotlin, Java, TypeScript, Python
Related Idea
Is this about generics?
Yes, this change fits into the existing generics design in Go by making the
const
constraint an interface type.The text was updated successfully, but these errors were encountered: