Skip to content

Structs

Structs are collections of fields. They are defined as types and then created using brackets to declare the values. You can also create structs with the new keyword which creates a pointer of type T and initializes the object with zero values.

go
import (
    "fmt"
)

type Vertex struct {
	X int
	Y int
}

v := Vertex{1, 2}
v2 := Vertex{X: 2, Y: 4}
fmt.Println(v.X)

Methods

Go does not have classes. Instead, functions can have receivers which relates to an instance of a struct and gets passed to the function. Methods can only be defined with a receiver that is in the same package, including built-in types.

By default, receivers are passed by value meaning any modifications to the receiver will not stick to the initial object. You can pass pointers as receivers to pass by reference and modify the reference. While functions require you to explicitly pass a pointer or a value, methods will automatically call the pointer method on a normal object and vice versa. In the below example, we call Scale on v and not (%v).Scale(10). Pointer receivers also don't require you to copy the object which can be expensive for larger objects.

go
package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func main() {
	v := Vertex{3, 4}
	fmt.Println(v.Abs()) // 5
    v.Scale(10)
	fmt.Println(v.Abs()) // 50
}

Interfaces

Interfaces are definitions of structs that all implement the declared methods. One note is that if the type implements the method with a pointer receiver, then the pointer to that class is considered that interface but the underlying type is not (If M took a *T, then you would not be able to instantiate an instance of T as I).

go
package main

import "fmt"

type I interface {
	M()
}

type T struct {
	S string
}

// This method means type T implements the interface I,
// but we don't need to explicitly declare that it does so.
func (t T) M() {
	fmt.Println(t.S)
}

func main() {
	var i I = T{"hello"}
	i.M()
}

You can also check that a pointer is nil within the function to gracefully handle nil pointer exceptions at the function level.

go
type T struct {
	S string
}

func (t *T) M() {
	if t == nil {
		fmt.Println("<nil>")
		return
	}
	fmt.Println(t.S)
}

Empty Interfaces

Empty interfaces in Go are essentially an any type.

go
package main

import "fmt"

func main() {
	var i interface{}
	// (<nil>, <nil>)

	i = 42
	// (42, int)

	i = "hello"
	// (hello, string)
}

type any interface{}

Type Assertions

To check if an instance of an interface is actually an instance of an underlying class, you can check with the below syntax.

go
t := i.(T) // Will panic if not type T

t, ok := i.(T) // Returns T instance and true if that type, or zero-value of T and false if not

// With switch
switch v := i.(type) {
case T:
    // here v has type T
case S:
    // here v has type S
default:
    // no match; here v has the same type as i
}

Common Interfaces

Stringers

This is an object that has a function to describe itself as a string. Many functions from fmt look for this method.

go
type Stringer interface {
    String() string
}

Errors

Return a string when the error occurs.

go
type error interface {
    Error() string
}