Go Empty Interface

Summary: in this tutorial, you will learn about the Go empty interface (interface {}) that can hold a value of any type.

Go interface {}

In Go, you can declare an interface without any methods:

interface {}

It is called an empty interface.

The empty interface is the most general type in Go’s type system because you can use it to represent any type.

Empty interfaces are flexible because you can use them in functions or data structures that can work with values of any type.

Defining generic functions

In the following program, we define a print() function that uses a parameter with the type is an empty interface:

package main

import "fmt"

func print(x interface{}) {
    fmt.Println(x)
}

func main() {
    print("Hi")
    print(100)
    print(true)
    print([]int{1, 2, 3, 4, 5})
    print([]string{"apple", "banana", "strawberry"})
}

Output:

Hi
100
true
[1 2 3 4 5]
[apple banana strawberry]

The print() function accepts any type and uses the Println function from the fmt package to write the value to the standard output, which is the screen.

Defining variadic functions with empty interfaces

To allow the print() function to accept a variable number of arguments, you can convert it to a variadic function as follows:

func print(args ...interface{}) {
	fmt.Println(args...)
}

In this syntax, the ..., which appears in front of the interface {}, denotes that the print() function can accept a variable number of arguments. Since the type of each argument is an interface {}, you can pass arguments of any type to the print() function.

The following program how to pass any number of arguments of any type to the print() function:

package main

import "fmt"

func print(args ...interface{}) {
    fmt.Println(args...)
}

func main() {
    print("Hi", 10)
}

Output:

print("Hi", 10)

Notice that the fmt.Println() function uses the type any, which is the alias of an empty interface (interface {}):

func Println(a ...any) (n int, err error) {
    return Fprintln(os.Stdout, a...)
}

Here’s the definition of the type any in the Go’s fmt package:

type any = interface{}

Defining data structures of any types

You can use empty interfaces to define a data structure such as arrays or slices. For example:

package main

import "fmt"

type AnySlice []interface{}

func main() {
    s := AnySlice{1, "one", 2.0, true}
    for _, v := range s {
        fmt.Println(v)
    }
}

How it works.

First, define an alias for a slice of empty interface {}, which is a slice of values of any type:

type AnySlice []interface{}

Second, declare and initialize a slice that can store a mix of values of any type:

s := AnySlice{1, "one", 2.0, true}

Third, iterate over the elements of the slice and display them on the screen:

for _, v := range s {
    fmt.Println(v)
}

Output:

1
one
2
true

Handling JSON with dynamic data

When working with JSON or other data formats where you don’t know the structure at compile time, you can use the interface {} to represent the data. For example:

package main

import (
    "encoding/json"
    "fmt"
    "os"
)

func main() {
    profileJSON := `{"name":"Anthony", "age":25, "tags":["golang", "programming"]}`
    var data map[string]interface{}

    if err := json.Unmarshal([]byte(profileJSON), &data); err != nil {
        fmt.Print(err)
        os.Exit(1)
    }

    fmt.Println(data)
}

Output:

map[age:25 name:Anthony tags:[golang programming]]

Type Assertions

In Go, type assertions allow you to retrieve the dynamic value stored in the interface {} variable and convert it back to its original type. For example:

package main

import (
    "fmt"
    "strings"
)

func main() {
    // store the value in an empty interface
    var x interface{} = "hello"

    // type assertion
    msg, ok := x.(string)

    // if x is a string
    if ok {
        fmt.Println(strings.ToLower(msg))
    } else {
        fmt.Println("x is not a string")
    }
}

How it works.

First, store a literal string in a variable of type interface {}:

var x interface{} = "hello"

Second, assert that the type of x is interface {}:

msg, ok := x.(string)

The x.(string) attempts to assert that the dynamic value stored in the variable x is of type string. It returns two values:

  • msg holds the asserted value if the assertion is successful.
  • ok is true if the assertion is successful and false otherwise.

Third, check the value of ok, if it is true, meaning the value stored in the x variable is a string:

if ok {
    fmt.Println(strings.ToLower(msg))
} else {
    fmt.Println("x is not a string")
}

Type Switch

In Go, you can use a type switch to process values of different types stored in an interface {} variable. For example:

package main

import "fmt"

func main() {
    values := []interface{}{"Apple", 100, 15.5, true}

    for _, value := range values {
        switch v := value.(type) {
        case int:
            fmt.Println("int:", v)
        case string:
            fmt.Println("string:", v)
        case bool:
            fmt.Println("bool:", v)
        default:
            fmt.Println("Unknown type")
        }
    }
}

Output:

string: Apple
int: 100
Unknown type
bool: true

Summary

  • An empty interface is an interface without method signatures.
  • An empty interface interface{} can represent a value of any type.
  • Use empty interfaces to create generic functions and data structures that handle dynamic data.
  • Use type assertions to retrieve the original values stored in an empty interface.
  • Use type switches to process values of different types stored in an empty interface.
Was this tutorial helpful?