DEV Community

loading...
Cover image for Golang Interfaces

Golang Interfaces

_tonoy_ profile image Ali Ahmed Tonoy ・4 min read

What is an Interface?

An interface defines a set of methods without their implementation. Also an interface cannot contains variables. An interface is declared in the format:

type Namer interface {
    Method(params) return type
}

Namer is a interface type. The name of an interface is formed by the method name plus the [er] suffix, such as Logger, Converter, Writer, and so on.

Go interfaces can have values that are a variable of the interface type or an interface value.

The interface variable both contains the value of the receiver value and a pointer to the appropriate method in a method table.

var anI Namer

An Example of Interface:

package main

import "fmt"

type Shaper interface {
    Area() float32
}

type Sqaure struct {
    side float32
}

func (sq *Square) Area(){
    return sq.side * sq.side
}

func main(){
    square := new(Square)
    square.side = 10
    areaInterface := Shaper(square)
    fmt.Printf("The square has area: %f\n", areaInterface.Area())
}

In the program above, we define an interface Shaper, with one method Area() returning float32 value and a struct of type Square, with one field side of type float32. We define a method Area() that can be called by a pointer to the Square type object. This method returns the area of a square sq. The struct Square implements the interface Shaper. Now, the interface variable contains a reference to the Square variable, and through it, we can call the method Area() on Square.

An Example from the standard library:

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

If we define a variable reader like this:

var reader io.Reader
// then the following is correct code
reader = os.Stdin
reader = bufio.NewReader(r)
reader = new(bytes.Buffer)
f,_ := os.Open("test.txt")
reader = bufio.NewReader(f)

Practice Time:

package main

import "fmt"

type Simpler interface {
    Get() int
    Set(n int)
}

type Simple struct {
    n int
}

func (si *Simple) Get() int {
    return si.n
}

func (si *Simple) Set(n int) {
    si.n = n
}
func FI(it Simpler) int {
    it.Set(10)
    return it.Get()
}

func main() {
    fmt.Println("Interface")

    s := new(Simple)
    inF := Simpler(s)
    fmt.Println(FI(inF))
    // shorthand 
    var sh Simple
    fmt.Println(FI(&sh))
}

What I have done in the above code:

Define an interface Simpler with methods Get(), which returns an integer, and Set() which has an integer as a parameter. Make a struct type Simple, which implements this interface.

Then, define a function that takes a parameter of the type Simpler and calls both methods upon it. Call this function from main to see if it all works correctly.

Embedded Interface and Type Assertions

An interface can contain the name of one (or more) interface(s), which is equivalent to explicitly enumerating the methods of the embedded interface in the containing interface.

type ReadWrite interface {
  Read(b Buffer) bool
  Write(b Buffer) bool
}

type Lock interface {
  Lock()
  Unlock()
}

type File interface {
  ReadWrite
  Lock
  Close()
}

Detecting and converting the type of an interface variable

An interface type variable varI can contain a value of any type; we must have a means to detect this dynamic type, which is the actual type of the value stored in the variable at run time. The dynamic type may vary during execution but is always assignable to the type of the interface variable itself.

In general, we can test if varI contains at a certain moment a variable of type T with the type assertion test:

// unchecked type assertion
v := varI.(T) 

// checked type assertion
if v, ok := varI.(T); ok { 
Process(v)
return
}else{
    // here varI is not of type T
}

Note: Always use the comma, ok form for type assertions

if v, ok := varI.(T); ok {
// ...
}

Type Assertions:

package main

import (
    "fmt"
    "math"
)

type Shaper interface {
    Area() float32
}

type Square struct {
    side float32
}

func (s *Square) Area() float32 {
    return s.side * s.side
}
func (ci *Circle) Area() float32 {
    return ci.radius * ci.radius * math.Pi
}

type Circle struct {
    radius float32
}

func main() {
    var aInf Shaper
    square := new(Square)
    // circle := new(Circle)
    square.side = 5
    // circle.radius = 10
    aInf = square
    // useing if, comma, ok
    if t, ok := aInf.(*Square); ok {
        fmt.Printf("The type of aInf is: %T\n", t)
    }
    if t, ok := aInf.(*Circle); ok {
        fmt.Printf("The type of aInf is: %T\n", t)
    } else {
        fmt.Println("aInf does not contain a variable of type Circle")
    }
    // using type switch
    switch t := aInf.(type) {
    case *Square:
        fmt.Printf("Type Square %T with value %v\n", t, t)

    case *Circle:
        fmt.Printf("Type Circle %T with value %v\n", t, t)

    default:
        fmt.Printf("Unexpected type %T", t)
    }

    fmt.Printf("The square has area: %f\n", aInf.Area())
}

Type assertion Example

package main

import "fmt"

type Simpler interface {
    Get() int
    Set(n int)
}

type Simple struct {
    n int
}

func (p *Simple) Get() int {
    return p.n
}

func (p *Simple) Set(u int) {
    p.n = u
}

type RSimple struct {
    n int
}

func (p *RSimple) Get() int {
    return p.n
}

func (p *RSimple) Set(u int) {
    p.n = u
}

func FI(it Simpler) int {
    switch it.(type) {
    case *Simple:
        it.Set(15)
        return it.Get()
    case *RSimple:
        it.Set(10)
        return it.Get()
    }
    return 0
}
func main(){
    var simpler Simple
    var rSimple RSimple
    // &simple is required because Get() is defined with the type pointer as receiver
    fmt.Println(FI(&simple))
    // &rSimple is required because Get() is defined with the type pointer as receiver
    fmt.Println(FI(&rSimple))
}

Thanks for Reading🌹

If you'd like to keep in touch with me, follow me on Twitter! DMs are open.

Discussion (0)

Forem Open with the Forem app