Interfaces as type constraints

May 5, 2023

If you’ve been playing around with interface types while reading about them for the last couple of weeks, you have probably noticed something: Many of the interface types described don’t work as variables. Here we see why:

General interfaces

Interfaces that are not basic may only be used as type constraints, or as elements of other interfaces used as constraints. They cannot be the types of values or variables, or components of other, non-interface types.

var x Float                     // illegal: Float is not a basic interface

var x interface{} = Float(nil)  // illegal

type Floatish struct {
	f Float                 // illegal

This makes for a potentially confusing situation: We can define a type that cannot be used as a variable.

So then what’s the point of such a type?

Well, they’re useful as type constraints in generic functions which, we’ll dig into later. But as a preview/refresher, here’s what that looks like:

type Int interface {
	int | int8 | int16 | int32 | uint | uint8 | uint16 | uint32

func add[T Int](a, b T) T {
	return a + b

So if that Int type cannot represent a variable, then of what type are the arguments a and b inside the above function?

Well, that depends on the type passed into the function! Let’s add a bit of debugging output to the function:

func add[T Int](a, b T) T {
	fmt.Printf("%T\n", a) // %T prints the argument's type
	return a + b

Now let’s call it with different input types and see what it does:

add(int(3), int(4))     // Prints: int
add(int32(5), int32(6)) // Prints: int32

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

Share this

Related Content

Implementing an interface

It feels like it’s been a month since we started on interfaces. Today we’re covering the final section on this topic! Implementing an interface A type T implements an interface I if T is not an interface and is an element of the type set of I; or T is an interface and the type set of T is a subset of the type set of I. A value of type T implements an interface if T implements the interface.

Recursive interfaces

Check out this week’s episode of the Cup o’ Go podcast: What the ʕ◔ϖ◔ʔ? New merch, TDD book interview with Adelina Simion, and more You can probably guess Go’s rules about recursively embedding interfaces. In short: It’s not allowed. General interfaces … An interface type T may not embed a type element that is, contains, or embeds T, directly or indirectly. // illegal: Bad may not embed itself type Bad interface { Bad } // illegal: Bad1 may not embed itself using Bad2 type Bad1 interface { Bad2 } type Bad2 interface { Bad1 } // illegal: Bad3 may not embed a union containing Bad3 type Bad3 interface { ~int | ~string | Bad3 } // illegal: Bad4 may not embed an array containing Bad4 as element type type Bad4 interface { [10]Bad4 } Note however, that, an interface method may refer to the interface itself.

Union restrictions

General interfaces … Implementation restriction: A union (with more than one term) cannot contain the predeclared identifier comparable or interfaces that specify methods, or embed comparable or interfaces that specify methods. Or putting this another way: Unions (that is, interface type parameters with a | character), only work on interfaces defined by other type parameters. interface { int | float32 // Valid int | error // Invalid: error is an interface that specifies a method int | comparable // Invalid: comparable is not a type parameter-defined interface } Quotes from _The Go Programming Language Specification_ Version of December 15, 2022