Go Interfaces

From NovaOrdis Knowledge Base
Jump to navigation Jump to search

External

Internal

Overview

An interface is a set of method signatures. The interface is declared with the keyword type, followed by the name of the interface, followed by the keyword interface and by the method signatures enclosed in a curly brace block. Each signature contains the method name, optional parameter names and mandatory parameter types, and optional return variable names and mandatory return types. Providing parameter and return names is recommended, as it arguably exposes more semantics, making the code clearer. See example, below.

The interface declaration contains no method implementations.

Interfaces are not fully-featured types, they're less than that, but they're used to express conceptual similarities between types. If several types implement the same interface, they are similar in a way that is important to the application. An interface highlights the similarities between the types implementing it and conceals the differences.

An interface does need to be formally declared on a target type for that type to implement it. Interfaces are satisfied implicitly. A type implements or satisfies an interface if the type defines all methods specified in the interface. Of course, the type can have other methods not specified by the interface. This is called duck typing. If a type implements an interface by the virtue of duck typing, a value of the type can be stored in a value of that interface type and the compiler ensure the assignments are correct at compile-time.

Interfaces can be thought of a kind of conceptual inheritance: types inherit behavior specified by an interface. Interfaces help supporting polymorphism in go: different types implement different behaviors specified by the same interface. For more details see:

Go Inheritance and Polymorphism

Go interfaces allow programs to model behavior rather than model types. The behavior is "attached" to existing types, via methods, and independently, the behavior is given a name, the name of the interface that groups together the methods. Go type system is structural, not nominal. The interfaces are behavior contracts, and they belong in the consuming code, not in the producing code. A common pattern should be to accept interfaces and return structs.

A type and its associated pointer type may or may not implement the same interface, depending on how the methods are associated with the value and pointer receivers of that type.

"The bigger the interface, the weaker the abstraction." The most important thing about interfaces in Go is that the smaller the interface is, the more useful it is. This is a Go-specific idea: we want to make little interfaces, so we can build components that share them.

Interfaces are defined at the "fault lines" of the system's architecture. These are between the main() function and the rest, and along the package API boundaries.

Interfaces should not be defined before a realistic example of usage exists.

Interfaces are rigid. For a library that exposes interfaces in the public API, any change to the interface requires releasing a new major version, since it breaks all third-party implementations.

Declaration

The following syntax declares an interface:

type <InterfaceName> interface {
  // Interface method set:
  <method_signature_1>
  <method_signature_2>
  ...
}

Example:

type SomeInterface interface {
  MethodA(int) (int, error)                          // No parameter and return value names are specified.
  MethodB(someValue int) (someResult int, err error) // Parameter and return value names are provided.
                                                     // This is arguably clearer, as it conveys more semantics.
}

What results after such a declaration is an interface type. For a discussion of concrete type vs. interface types, see:

Go Language | Concrete vs. Interface Types

Interface Method Set

An interface method set the list of method declaration specified in the interface type declaration. The interface type declaration has the only purpose of defining the method set and giving it a name, which is the interface name. Also see:

Method Sets

Naming

Single-method interfaces are named using the method name plus an "er" suffix. An interface with just one Write method would be called Writer. Sometimes, the result isn't correct English, but we do it anyway. In other cases, we use English to make the result nicer:

type ByteReader interface {
    ReadByte() (c byte, err error)
}

When an interface includes multiple methods, choose a name that accurately describes its purpose.

Also see:

Go Style

Implementing an Interface

As mentioned above, interfaces are not formally declared to be satisfied with dedicated syntax, they are satisfied implicitly. The type satisfying an interface do not need to formally declare the name of the interface anywhere in its definition. Instead, it is sufficient to declare themselves as value or pointer receiver types for all the methods in the interface declaration.

type SomeImplementation { ... } // SomeInterface is specified nowhere in the SomeImplementation declaration

// Declaring the type as pointer receiver for *all* methods declared in the interface makes 
// the type to implement that interface . Value and pointer receivers must not be mixed by 
// the method implementations of an interface.
func (s *SomeImplementation) MethodA(i int) (int, error) { ... }
func (s *SomeImplementation) MethodB(someValue int) (int, error) { ... }

The method signatures from the interface method set are independent on whether the corresponding methods from the concrete types are declared with value or pointer receivers.

Interface Values

Declaring a variable of an interface type creates an interface value.

The interface value is a pair that contains a dynamic type and a dynamic value. The dynamic type is a concrete type that implements that interface. The dynamic value is an instance of the dynamic type:

var i SomeInterface // i is a pair: (dynamic_type, dynamic_value)

Assuming the following interface and concrete type declarations:

type SomeInterface interface {
  MethodA()
}

type SomeImplementation struct {
  data string
}

// MethodA makes SomeImplementation implement the interface.
func (s *SomeImplementation) MethodA() {
  if s == nil {
    fmt.Println("invocation with a nil dynamic value")
  } else {
    fmt.Printf("data: %s\n", s.data)    
  }
}

... the following variable declarations and initializations create an interface variable and a concrete type variable. The code then assigns the interface variable with the concrete type variable, which will make the interface value get the pair of a dynamic type ( SomeImplementation ) and the dynamic value (t):

var i SomeInterface
var t *SomeImplementation
t = &SomeImplementation{"blue"}
i = t

The interface value can be then used to invoke the method on the dynamic concrete type instance (the dynamic value).

i.MethodA() // prints "data: blue"

An interface value can have a nil dynamic value, and still be usable, as long it is assigned a dynamic type with this syntax:

var i SomeInterface
var t *SomeType // Declares "t" a pointer to the concrete type, not the concrete type
i = t

An interface value assigned with a pointer to the dynamic type, but not with a dynamic value can still be invoked into, given that the method implementation cases for the situation when the receiver type implicit argument is nil. In the example above, we assigned i a dynamic type, SomeImplementation, but not a dynamic value, as t has no concrete value yet. The dynamic type is enough information to go find which implementation MethodA() should use.

That is why it is a good idea to guard against nil implicit argument in all concrete methods provided by types that implement interfaces.

This way we get a static (in the Java or Python sense of the word) method and an instance method at the same time.

i.MethodA() // prints "invocation with a nil dynamic value"

An interface value that carries a dynamic type but no dynamic value is a legal state. It is a way to implement static (type) methods.

nil Interface Value

This describes an interface value with a nil dynamic type. In this situation, you cannot call the methods on that interface, because without a dynamic type you can't know which method you are referring to.

Interfaces as Function Parameters and Result Values

Interface as Function Parameter

When a function is declared with an interface type parameter, the function can be invoked with either a value or a pointer of the implementing type, as long the implementation uses a value receiver type. For the following interface and type declaration:

type SomeInterface interface {
    MethodA()
}

type SomeImplementation struct {
    data string
}

the type may chose to implement the interface by using a value receiver:

func (s SomeImplementation) MethodA() {
    fmt.Printf("data: %s\n", s.data)
}

In this case, if a function is declared with a parameter of SomeInterface interface type, the function can be invoked by passing either a value or a pointer to the type instance:

func SomeFunc(i SomeInterface) {
    i.MethodA()
}

...

t := SomeImplementation{data: "blue"}
SomeFunc(t)

// this also works:
t2 := &SomeImplementation{data: "red"}
SomeFunc(t2)

If the implementation of the interface is done with a pointer receiver:

func (s *SomeImplementation) MethodA() {
    fmt.Printf("data: %s\n", s.data)
}

then only a pointer to the implementing type instance can be passed to the function SomeFunc():

t := &SomeImplementation{data: "green"}
SomeFunc(t)

If we attempt to pass an implementing type instance value, the compiler complains:

Cannot use t (variable of type SomeImplementation) as SomeInterface value in argument to SomeFunc: SomeImplementation does not implement SomeInterface (method MethodA has pointer receiver)

Interface as Function Result

Functions can return interfaces as results, as shown below. However, in general, functions should accept interfaces and return structs.

type SomeInterface interface {
  ...
}

type SomeImplementation struct {
  ... 
}

 // the type SomeImplementation  is presumably declared as receiver type for all interface methods, thus implements it 

func someFunc() SomeInterface {
  return &SomeImplementation{} // this is a valid function declaration
}

Empty Interface interface{} any

An empty interface, declared as interface{} or any is an interface that specifies no methods. Any type can "implement" that interface. If you want to declare a parameter of any type for a function, make it an empty interface:

func SomeFunc(v interface{}) {
  ...
}
func SomeFunc(v any) {
  ...
}

According to the builtin package documentation, any is an alias for interface{} and it is equivalent to interface{} in all ways.

Rob Pike does not like empty interfaces: when you're programming and you use an empty interface, think really hard if this is actually what you want, or whether isn't just a little something you can put in, an interface with an actual method that is really necessary to capture the things you are talking about.

Type Assertions

Type Assertions

Interfaces as Embedded Types

Interface names can be used as embedded types in structs.

Using Interfaces

Function with Multiple Types of Parameters

Interface expresses similarity, so make the function take as a parameter an interface that expresses the similarity of all desired arguments.

func Area(s Shape) {
  ...
}

Mocking

See:

Testify Mocks