Type equations

I don’t have a lot to expand on in this section, except to offer a pre-amble about some technical terms and symbols that won’t be familiar to everyone: ≡ is a symbol from mathematics that means “identical to”. It’s similar to = which we’re all famliar with, but “stricter”. The spec uses variants of this symbol in some following equations, so it’s nice to understand what the symbol means originally. ≡A means “are assignable to each other”, as in: X ≡A Y means “X is assignable to Y and Y is assignable to X” ≡C means “satisifes constraint”, as in: X ≡C ~int means “X satisfies constraint ~int”.


Type inference

We just covered instantiations, and learned that it is often possible to infer generic types. Nowe we’ll examine how that mechanism works. This bit gets a bit into the weeds. You’ll be forgiven if you choose to skip over this. Type inference 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.


Partial type argument lists

Instantiations … A partial type argument list cannot be empty; at least the first argument must be present. The list is a prefix of the full list of type arguments, leaving the remaining arguments to be inferred. Loosely speaking, type arguments may be omitted from “right to left”. func apply[S ~[]E, E any](s S, f func(E) E) S { … } f0 := apply[] // illegal: type argument list cannot be empty f1 := apply[[]int] // type argument for S explicitly provided, type argument for E inferred f2 := apply[[]string, string] // both type arguments explicitly provided var bytes []byte r := apply(bytes, func(byte) byte { … }) // both type arguments inferred from the function arguments Let’s demonstrate this by refering to the example from yesterday.


When function instantiations can be inferred

A quick update on my livestream: It’s on hold until February, as my family and I made a bit of a last-minute trip to visit family for the month of January, so I won’t be in my studio for a while. Instantiations … When using a generic function, type arguments may be provided explicitly, or they may be partially or completely inferred from the context in which the function is used.


Instantiations

Instantiations A generic function or type is instantiated by substituting type arguments for the type parameters. Instantiation proceeds in two steps: Each type argument is substituted for its corresponding type parameter in the generic declaration. This substitution happens across the entire function or type declaration, including the type parameter list itself and any types in that list. After substitution, each type argument must satisfy the constraint (instantiated, if necessary) of the corresponding type parameter.


Passing slices to variadic functions

Passing arguments to ... parameters … If the final argument is assignable to a slice type []T and is followed by ..., it is passed unchanged as the value for a ...T parameter. In this case no new slice is created. Given the slice s and call s := []string{"James", "Jasmine"} Greeting("goodbye:", s...) within Greeting, who will have the same value as s with the same underlying array. This leads to a few gotchas that I’ll spend today talking about.


Passing arguments to ... parameters

If you’ve been a reader for a while, you may recall back in April when I first talked about variadic functions. Now we’re ready to dig in a bit further to the details of how these mysterious creatures work. First we’ll see how they work “from the inside”. That is, from the perspective of someone writing a variadic function. Passing arguments to ... parameters If f is variadic with a final parameter p of type .

Subscribe to Boldly Go: Daily

Every day I'll send you advice to improve your understanding of Go. Don't miss out! I will respect your inbox, and honor my privacy policy.

Unsure? Browse the archive.


Refresher on methods

In yesterday’s email, I failed to include the example included in the spec, which doesn’t make sense to include otherwise. I’ve updated the web version of the email in case you’re super curious to see what I left out. We’ve already covered these details, but they’re logically mentioned again in the section about function calls, so we’ll cover them here: Calls … A method call x.m() is valid if the method set of (the type of) x contains m and the argument list can be assigned to the parameter list of m.


Special case: Multi-value arguments to functions

You may recall that when we started the section on function calls, we were told about a “special case”… well here it is! Calls … As a special case, if the return values of a function or method g are equal in number and individually assignable to the parameters of another function or method f, then the call f(g(parameters_of_g)) will invoke f after binding the return values of g to the parameters of f in order.


nil functions

Calls … Calling a nil function value causes a run-time panic. So, calling a nil function causes a panic. Sensible enough. But when would you ever run across a nil function? Let’s look at some examples. This is probably the most obvious example, and perhaps one that popped to your mind: var fn func(int) // fn is of type func(int), but uninitialized, so nil fn(3) // run-time panic But let’s consider another example, which can be a bit more confusing.


Evaluation of function parameters

Calls If f denotes a generic function, it must be instantiated before it can be called or used as a function value. We’ve seen this rule applied to methods and other types. It should be standard by now, so I won’t dwell on it. In a function call, the function value and arguments are evaluated in the usual order. After they are evaluated, the parameters of the call are passed by value to the function and the called function begins execution.