The many type equations

January 9, 2024

Type inference

Type inference supports calls of generic functions and assignments of generic functions to (explicitly function-typed) variables.

So just to call out this point, made in passing: You cannot assign the result of a generic function to a generic, non-instantiated type, even if inferrence should be intuitive. Do demonstrate:

type S[T ~int | ~float64] struct {
	Value T
}

func sum[V ~int | ~float32](a, b V) V {
	return a + b
}

// cannot use generic type S[T ~int | ~float64] without instantiation
x := S{
	Value: sum(int(1), int(2)),
}

Even though the type returned from sum() is obviously int, we cannot do this assignment. We must instantiate the type S explicitly:

x := S[int]{
	Value: sum(int(1), int(2)),
}

See it in the playground

But, we can pass the results of generic functions to other generic functions:

… 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 typeof(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 r of function type: typeof(r) ≡A typeof(f).

Additionally, each type parameter Pk and corresponding type constraint Ck yields the type equation PkC Ck.

Are you yawning as much as I am?

In summary, after the entire function call is expanded, we match all the arguments passed to the function (and functions passed to the function), to their associated parameters.

Then we end up with a list of equations of forms similar to:

  • typeof(v) ≡A typeof(f)
  • typeof(p1) ≡A typeof(a1)
  • typeof(p2) ≡A typeof(a2)
  • etc…

Then we can begin solving the equatoins.

Well, actually not yet… There’s one more small step to consider, first. Tomorrow.

Quotes from The Go Programming Language Specification Version of August 2, 2023


Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Related Content


Bound type parameters

Type inference … 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.


Exact & loose type unification

Type unification … Unification uses a combination of exact and loose unification depending on whether two types have to be identical, assignment-compatible, or only structurally equal. The respective type unification rules are spelled out in detail in the Appendix. The precise definitions of “exact” and “loose” unification are buried in the appendix, and depend on the specific types involved. In general, I think it’s not terribly inaccurate to say that exact unification applies when the two types are identical, for composite types with identical structure (i.


Type unification

A couple of days ago we saw the spec reference the concept of “type unification”. Today we start through that explanation…. Type unification Type inference solves type equations through type unification. Type unification recursively compares the LHS and RHS types of an equation, where either or both types may be or contain bound type parameters, and looks for type arguments for those type parameters such that the LHS and RHS match (become identical or assignment-compatible, depending on context).