From: Robert Griesemer
A type definition creates a new, distinct type with the same
-underlying type and operations as the given type
+underlying type and operations as the given type
and binds an identifier, the type name, to it.
-NOTE: This section is not yet up-to-date for Go 1.21. +A use of a generic function may omit some or all type arguments if they can be +inferred from the context within which the function is used, including +the constraints of the function's type parameters. +Type inference succeeds if it can infer the missing type arguments +and instantiation succeeds with the +inferred type arguments. +Otherwise, type inference fails and the program is invalid.
-Missing function type arguments may be inferred by a series of steps, described below. -Each step attempts to use known information to infer additional type arguments. -Type inference stops as soon as all type arguments are known. -After type inference is complete, it is still necessary to substitute all type arguments -for type parameters and verify that each type argument -implements the relevant constraint; -it is possible for an inferred type argument to fail to implement a constraint, in which -case instantiation fails. +Type inference uses the type relationships between pairs of types for inference: +For instance, a function argument must be assignable +to its respective function parameter; this establishes a relationship between the +type of the argument and the type of the parameter. +If either of these two types contains type parameters, type inference looks for the +type arguments to substitute the type parameters with such that the assignability +relationship is satisfied. +Similarly, type inference uses the fact that a type argument must +satisfy the constraint of its respective +type parameter.
-Type inference is based on +Each such pair of matched types corresponds to a type equation containing +one or multiple type parameters, from one or possibly multiple generic functions. +Inferring the missing type arguments means solving the resulting set of type +equations for the respective type parameters. +
+ ++For example, given +
+ ++// dedup returns a copy of the argument slice with any duplicate entries removed. +func dedup[S ~[]E, E comparable](S) S { ⦠} + +type Slice []int +var s Slice +s = dedup(s) // same as s = dedup[Slice, int](s) ++ +
+the variable s
of type Slice
must be assignable to
+the function parameter type S
for the program to be valid.
+To reduce complexity, type inference ignores the directionality of assignments,
+so the type relationship between Slice
and S
can be
+expressed via the (symmetric) type equation Slice â¡A S
+(or S â¡A Slice
for that matter),
+where the A
in â¡A
+indicates that the LHS and RHS types must match per assignability rules
+(see the section on type unifcation for
+details).
+Similarly, the type parameter S
must satisfy its constraint
+~[]E
. This can be expressed as S â¡C ~[]E
+where X â¡C Y
stands for
+"X
satisfies constraint Y
".
+These observations lead to a set of two equations
+
+ Slice â¡A S (1) + S â¡C ~[]E (2) ++ +
+which now can be solved for the type parameters S
and E
.
+From (1) a compiler can infer that the type argument for S
is Slice
.
+Similarly, because the underlying type of Slice
is []int
+and []int
must match []E
of the constraint,
+a compiler can infer that E
must be int
.
+Thus, for these two equations, type inference infers
+
+ S â Slice + E â int ++ +
+Given a set of type equations, the type parameters to solve for are
+the type parameters of the functions that need to be instantiated
+and for which no explicit type arguments is provided.
+These type parameters are called bound type parameters.
+For instance, in the dedup
example above, the type parameters
+P
and E
are bound to dedup
.
+An argument to a generic function call may be a generic function itself.
+The type parameters of that function are included in the set of bound
+type parameters.
+The types of function arguments may contain type parameters from other
+functions (such as a generic function enclosing a function call).
+Those type parameters may also appear in type equations but they are
+not bound in that context.
+Type equations are always solved for the bound type parameters only.
+
+Type inference supports calls of generic functions and assignments +of generic functions to (explicitly function-typed) variables. +This includes passing generic functions as arguments to other +(possibly also generic) functions, and returning generic functions +as results. +Type inference operates on a set of equations specific to each of +these cases. +The equations are as follows (type argument lists are omitted for clarity):
+ For a function call f(a0, a1, â¦)
where
+ f
or a function argument ai
is
+ a generic function:
+
+ Each pair (ai, pi)
of corresponding
+ function arguments and parameters where ai
is not an
+ untyped constant yields an equation
+ typeof(pi) â¡A typeof(ai)
.
+
+ If ai
is an untyped constant cj
,
+ and pi
is a bound type parameter Pk
,
+ the pair (cj, Pk)
is collected separately from
+ the type equations.
+
+ For an assignment v = f
of a generic function f
to a
+ (non-generic) variable v
of function type:
+
+ typeof(v) â¡A typeof(f)
.
+
+ For a return statement return â¦, f, â¦
where f
is a
+ generic function returned as a result to a (non-generic) result variable
+ of function type:
+
+ typeof(r) â¡A typeof(f)
.
+
-and then proceeds with the following steps:
+Additionally, each type parameter Pk
and corresponding type constraint
+Ck
yields the type equation
+Pk â¡C Ck
.
+
+Type inference gives precedence to type information obtained from typed operands +before considering untyped constants. +Therefore, inference proceeds in two phases:
+ The type equations are solved for the bound + type parameters using type unification. + If unification fails, type inference fails. +
+ For each bound type parameter Pk
for which no type argument
+ has been inferred yet and for which one or more pairs
+ (cj, Pk)
with that same type parameter
+ were collected, determine the constant kind
+ of the constants cj
in all those pairs the same way as for
+ constant expressions.
+ The type argument for Pk
is the
+ default type for the determined constant kind.
+ If a constant kind cannot be determined due to conflicting constant kinds,
+ type inference fails.
+
-If there are no ordinary or untyped function arguments, the respective steps are skipped. -Constraint type inference is skipped if the previous step didn't infer any new type arguments, -but it is run at least once if there are missing type arguments. +If not all type arguments have been found after these two phases, type inference fails.
-The substitution map M is carried through all steps, and each step may add entries to M. -The process stops as soon as M has a type argument for each type parameter or if an inference step fails. -If an inference step fails, or if M is still missing type arguments after the last step, type inference fails. +If the two phases are successful, type inference determined a type argument for each +bound type parameter: +
+ ++ Pk â Ak ++ +
+A type argument Ak
may be a composite type,
+containing other bound type parameters Pk
as element types
+(or even be just another bound type parameter).
+In a process of repeated simplification, the bound type parameters in each type
+argument are substituted with the respective type arguments for those type
+parameters until each type argument is free of bound type parameters.
+
+If type arguments contain cyclic references to themselves +through bound type parameters, simplification and thus type +inference fails. +Otherwise, type inference succeeds.
+ +Note: This section is not up-to-date for Go 1.21. + +
+
Type inference is based on type unification. A single unification step
applies to a substitution map and two types, either
@@ -4546,264 +4698,6 @@ and the type literal
-Function argument type inference infers type arguments from function arguments:
-if a function parameter is declared with a type
-For instance, given the generic function
-
-and the call
-
-the type argument for
-Inference happens in two separate phases; each phase operates on a specific list of
-(parameter, argument) pairs:
-
-Any other (parameter, argument) pair is ignored.
-
-By construction, the arguments of the pairs in Lu are untyped constants
-(or the untyped boolean result of a comparison). And because default types
-of untyped values are always predeclared non-composite types, they can never match against
-a composite type, so it is sufficient to only consider parameter types that are single type
-parameters.
-
-Each list is processed in a separate phase:
-
-While unification is successful, processing of each list continues until all list elements
-are considered, even if all type arguments are inferred before the last list element has
-been processed.
-
-Example:
-
-In the example
-Constraint type inference infers type arguments by considering type constraints.
-If a type parameter
-For instance, consider the type parameter list with type parameters
-Constraint type inference can deduce the type of
-unifying the underlying type of
-Using the core type of a constraint may lose some information: In the (unlikely) case that
-the constraint's type set contains a single defined type
-
-Generally, constraint type inference proceeds in two phases: Starting with a given
-substitution map M
-
-The result of constraint type inference is the final substitution map M from type
-parameters
-For instance, given the type parameter list
-
-and the single provided type argument
-In the first phase, the type parameters
-At this point there are two entries in M where the right-hand side
-is or contains type parameters for which there exists other entries in M:
-
-
-Replace
-
-Replace
-
-At this point no further substitution is possible and the map is full.
-Therefore,
@@ -5479,7 +5373,7 @@ in any of these cases:
ignoring struct tags (see below),
[]E
, unification compares []float64
-
Function argument type inference
-
-
-
-T
that uses
-type parameters,
-unifying the type of the corresponding
-function argument with T
may infer type arguments for the type
-parameters used by T
.
-
-func scale[Number ~int64|~float64|~complex128](v []Number, s Number) []Number
-
-
-
-var vector []float64
-scaledVector := scale(vector, 42)
-
-
-Number
can be inferred from the function argument
-vector
by unifying the type of vector
with the corresponding
-parameter type: []float64
and []Number
-match in structure and float64
matches with Number
.
-This adds the entry Number
→ float64
to the
-substitution map.
-Untyped arguments, such as the second function argument 42
here, are ignored
-in the first round of function argument type inference and only considered if there are
-unresolved type parameters left.
-
-
-
-
-
-
-
-func min[T ~int|~float64](x, y T) T
-
-var x int
-min(x, 2.0) // T is int, inferred from typed argument x; 2.0 is assignable to int
-min(1.0, 2.0) // T is float64, inferred from default type for 1.0 and matches default type for 2.0
-min(1.0, 2) // illegal: default type float64 (for 1.0) doesn't match default type int (for 2)
-
-
-min(1.0, 2)
, processing the function argument 1.0
-yields the substitution map entry T
→ float64
. Because
-processing continues until all untyped arguments are considered, an error is reported. This
-ensures that type inference does not depend on the order of the untyped arguments.
-Constraint type inference
-
-P
has a constraint with a
-core type C
,
-unifying P
with C
-may infer additional type arguments, either the type argument for P
,
-or if that is already known, possibly the type arguments for type parameters
-used in C
.
-List
and
-Elem
:
-
-[List ~[]Elem, Elem any]
-
-
-Elem
from the type argument
-for List
because Elem
is a type parameter in the core type
-[]Elem
of List
.
-If the type argument is Bytes
:
-
-type Bytes []byte
-
-
-Bytes
with the core type means
-unifying []byte
with []Elem
. That unification succeeds and yields
-the substitution map entry
-Elem
→ byte
.
-Thus, in this example, constraint type inference can infer the second type argument from the
-first one.
-N
, the corresponding core type is N
's underlying type rather than
-N
itself. In this case, constraint type inference may succeed but instantiation
-will fail because the inferred type is not in the type set of the constraint.
-Thus, constraint type inference uses the adjusted core type of
-a constraint: if the type set contains a single type, use that type; otherwise use the
-constraint's core type.
-
-
-
-P
→ A
in M where A
is or
-contains type parameters Q
for which there exist entries
-Q
→ B
in M, substitute those
-Q
with the respective B
in A
.
-Stop when no further substitution is possible.
-P
to type arguments A
where no type parameter P
-appears in any of the A
.
-
-[A any, B []C, C *A]
-
-
-int
for type parameter A
,
-the initial substitution map M contains the entry A
→ int
.
-B
and C
are unified
-with the core type of their respective constraints. This adds the entries
-B
→ []C
and C
→ *A
-to M.
-
-[]C
and *A
.
-In the second phase, these type parameters are replaced with their respective
-types. It doesn't matter in which order this happens. Starting with the state
-of M after the first phase:
-A
→ int
,
-B
→ []C
,
-C
→ *A
-A
on the right-hand side of → with int
:
-A
→ int
,
-B
→ []C
,
-C
→ *int
-C
on the right-hand side of → with *int
:
-A
→ int
,
-B
→ []*int
,
-C
→ *int
-M
represents the final map of type parameters
-to type arguments for the given type parameter list.
-Operators
x
's type and T
are not
type parameters but have
- identical underlying types.
+ identical underlying types.
A Pointer
is a pointer type but a Pointer
value may not be dereferenced.
-Any pointer or value of underlying type uintptr
can be
+Any pointer or value of underlying type uintptr
can be
converted to a type of underlying type Pointer
and vice versa.
The effect of converting between Pointer
and uintptr
is implementation-defined.