mdawar.dev

A blog about programming, Web development, Open Source, Linux and DevOps.

Go - Interfaces

Declaration

An interface type specifies a set of methods that a concrete type must have to implement the interface.

go
// An interface `I` that specifies a single method `M`.
type I interface {
  M()
}

Interface Satisfaction

  • Interfaces are satisfied implicitly
  • There’s no need to declare the interfaces that a concrete type satisfies
  • A type satisfies an interface simply by implementing all the methods specified by the interface
  • We can create new interfaces that are satisfied by existing types without changing them
go
// An example interface.
type Speaker interface {
  Speak() string
}

// An example type that implements the `Speaker` interface.
type Person struct {
  Name string
}

// Interfaces are implemented implicitly.
// The struct type `Person` implements the `Speaker` interface
// by defining a method `Speak()` with the same signature
// without any explicit declaration.
func (p Person) Speak() string {
  return "Hello, my name is" + p.Name
}

// A type may implement several interfaces.
// Declaring a `String()` method makes a type satisfy the `fmt.Stringer`
// interface from the standard library (fmt package).
func (p Person) String() string {
  return p.Name
}

// The `fmt.Stringer` interface is defined like this.
type Stringer interface {
  String() string
}

Embedded Interfaces

Interfaces may be defined as a combination of other interfaces.

go
// An interface with a `Download` method.
type Downloader interface {
  Download(url string) string
}

// An interface with an `Extract` method.
type Extractor interface {
  Extract(file string) bool
}

// An interface that embeds other interfaces.
// Embedding an interface is a shorthand for defining all of its methods.
type DownloadExtractor interface {
  // This interface embeds both `Downloader` and `Extractor`.
  // The type set of this interface now includes `Download` and `Extract` methods.
  Downloader
  Extractor
}

// An interface can embed other interfaces and define its own set of methods.
type DownloadCompressor interface {
  Downloader
  Compress(file string) string // Explicitly declared method
}

Example from the io package of the standard library.

go
type Reader interface {
	Read(p []byte) (n int, err error)
}

type Writer interface {
	Write(p []byte) (n int, err error)
}

type Closer interface {
	Close() error
}

// `ReadWriter` interface embeds `Reader` and `Writer`
// it groups the `Read` and `Write` methods.
type ReadWriter interface {
	Reader
	Writer
}

// `ReadWriteCloser` interface embeds `Reader`, `Writer` and `Closer`
// it groups the `Read`, `Write` and `Close` methods.
type ReadWriteCloser interface {
	Reader
	Writer
	Closer
}

Interface Values

An interface value can hold any value of a type that satisfies the interface.

An interface value has 2 components:

  1. Dynamic type (A concrete type)
  2. Dynamic value (A value of that type)
go
// An interface.
type Speaker interface {
  Speak() string
}

// Declare a variable of an interface type.
// The zero value of an uninitialized variable of interface type is nil.
// Both components the type and value are nil.
var s Speaker         // nil

fmt.Println(s == nil) // true

// Calling a method on a nil interface is a run-time error.
s.Speak() // panic: runtime error
go
// An interface that defines a `Speak` method.
type Speaker interface {
  Speak() string
}

// A type that implements the `Speaker` interface.
type Person struct {
  Name string
}

// Implementation of the `Speak` method that makes
// the `Person` type implement the `Speaker` interface.
func (p Person) Speak() string {
  return "Hello, my name is " + p.Name
}

var s Speaker // nil

p := Person{"John"}

// An expression may be assigned to an interface only if its type
// satisfies the interface.
// The interface's dynamic value now holds the `Person` instance
// and the dynamic type holds the `Person` type.
s = p // Person satsifies Speaker

// Equivalent to the explicit conversion from a concrete type
// to an interface type.
s = Speaker(p) // Explicitly convert Person to Speaker

// Calling a method on an interface value executes the method
// of the same name on its underlying type.
s.Speak() // "Hello, my name is John"

// Assigning nil to the interface value
// resets both components (type and value) to nil.
s = nil // Same state when it was declared

Comparison

Interface values may be compared using == and !=.

Interface values are equal if:

  1. Both are nil
  2. Their dynamic types are identical and their dynamic values are equal

Interface values should only be compared if we are certain that they contain comparable types, if the interface values are not comparable (e.g. slice) then the comparison fails with a panic.

Handling a nil Receiver

go
type Speaker interface {
  Speak() string
}

// A type that implements the `Speaker` interface.
type Person struct {
  Name string
}

// We need a pointer receiver to check for a nil receiver.
func (p *Person) Speak() string {
  if p == nil {
    // The method was called with a nil receiver.
    return "Hello"
  }

  return "Hello, my name is " + p.Name
}

// A nil interface that does not hold any value.
var s Speaker // nil

// Declare a pointer to `Person`.
var p *Person // nil

// An interface value holding a nil value is not a nil interface.
// The interface's dynamic value now holds a nil pointer
// and the dynamic type holds the *Person type.
s = p // Person implements Speaker

// The value inside the interface is nil.
// The method will be called with a nil receiver.
s.Speak() // "Hello"

Value vs Pointer Receiver

go
type Speaker interface {
  Speak() string
}

type Person struct {
  Name string
}

// Method with a pointer receiver.
func (p *Person) Speak() string {
  return "Hello, my name is " + p.Name
}

person := Person{"Chris"}

// Pointer to `Person`.
p := &person // *Person

// `Speak` is defined on the type `*Person`.
p.Speak() // Call Speak on a pointer of type *Person

// We can call a method defined on a pointer receiver with a value
// as long as the argument is a variable (the compiler takes its address).
person.Speak()
// Equivalent to
(&person).Speak()

// We cannot call it on a non-addressable value.
Person{"Chris"}.Speak() // cannot call pointer method Speak on Person

// Only `*Person` has a `Speak` method so it satisfies the `Speaker` interface.
// `Person` does not have a `Speak` method so it does not satisfy the interface.
var _ Speaker = &person // OK
var _ Speaker = person  // Person does not implement Speaker

Compile-time Satisfaction Check

To assert that a type satisfies an interface at compile time, we can use a declaration like the following:

go
// `*Person` must satisfy `Speaker` (If not the compiler will report an error).
var _ Speaker = (*Person)(nil)
  1. We declare an unnamed variable _ of the interface type to check (Speaker), the variable won’t be used, the only purpose of this declaration is to verify that the type (*Person) implements the interface (Speaker)
  2. The expression (*Person)(nil) converts nil to the type *Person without creating a new instance, it only provides a value for the assignment
  3. If the type does not implement the interface, the compiler will report an error because the assignment will be invalid