Core types

May 24, 2023

Yesterday we discussed underlying types. Today we’re looking at the somewhat more essoteric concept of a core type. The concept of a core type, as distinct (sometimes) from the underlying type exists for the benefit of some generics-related constructs. This will make more sense as we get to these uses of core types in the future. So for now we’re just going to breeze through the description, without diving into additional detail than what is provided in the spec.

Core types

Each non-interface type T has a core type, which is the same as the underlying type of T.

An interface T has a core type if one of the following conditions is satisfied:

  1. There is a single type U which is the underlying type of all types in the type set of T; or
  2. the type set of 1 contains only channel types with identical element type E, and all directional channels have the same direction.

No other interfaces have a core type.

The core type of an interface is, depending on the condition that is satisfied, either:

  1. the type U; or
  2. the type chan E if T contains only bidirectional channels, or the type chan<- E or <-chan E depending on the direction of the directional channels present.

By definition, a core type is never a defined type, type parameter, or interface type.

Examples of interfaces with core types:

type Celsius float32
type Kelvin  float32

interface{ int }                          // int
interface{ Celsius|Kelvin }               // float32
interface{ ~chan int }                    // chan int
interface{ ~chan int|~chan<- int }        // chan<- int
interface{ ~[]*data; String() string }    // []*data

Examples of interfaces without core types:

interface{}                               // no single underlying type
interface{ Celsius|float64 }              // no single underlying type
interface{ chan int | chan<- string }     // channels have different element types
interface{ <-chan int | chan<- int }      // directional channels have different directions

Some operations (slice expressions, append and copy) rely on a slightly more loose form of core types which accept byte slices and strings. Specifically, if there are exactly two types, []byte and string, which are the underlying types of all types in the type set of interface T, the core type of T is called bytestring.

Examples of interfaces with bytestring core types:

interface{ int }                          // int (same as ordinary core type)
interface{ []byte | string }              // bytestring
interface{ ~[]byte | myString }           // bytestring

Note that bytestring is not a real type; it cannot be used to declare variables are[sic*] compose other types. It exists solely to describe the behavior of some operations that read from a sequence of bytes, which may be a byte slice or a string.

*This has been corrected to “or” in the next edition, expected to be published in August with Go 1.21.

Quotes from The Go Programming Language Specification Version of December 15, 2022


Share this

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

Unsure? Browse the archive .

Related Content


Arithmetic operators

Arithmetic operators Arithmetic operators apply to numeric values and yield a result of the same type as the first operand. The four standard arithmetic operators (+, -, *, /) apply to integer, floating-point, and complex types; + also applies to strings. The bitwise logical and shift operators apply to integers only. + sum integers, floats, complex values, strings - difference integers, floats, complex values * product integers, floats, complex values / quotient integers, floats, complex values % remainder integers & bitwise AND integers | bitwise OR integers ^ bitwise XOR integers &^ bit clear (AND NOT) integers << left shift integer << integer >= 0 >> right shift integer >> integer >= 0 There should be nothing surprising here.


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).