Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Go function types that return structs being used with interfaces

Tags:

function

go

I have a struct in a package that has time consuming methods on it and is also time consuming to construct via it's factory function. Therefore, in the package that depends on this other struct I would like to be able to test it using both a fake factory function and a fake struct once it has been created. As the struct is constructed via a factory function I would like to fake the factory function and pass an alternative factory function into my struct during testing time.

An example of the expensive package would be:

package expensive

import "fmt"

type myStruct struct{}

func (m *myStruct) DoSomething() {
    fmt.Println("In Do something")
}

func (m *myStruct) DoSomethingElse() {
    fmt.Println("In do something else")
}

// CreateInstance is expensive to call
func CreateInstance() *myStruct {
    return &myStruct{}
}

My main package that uses this then looks like this:

package main

import "play/expensive"

func main() {
    thing := structToConstruct{expensive.CreateInstance}
    thing.performAction()
}

type myInterface interface {
    DoSomething()
}

type structToConstruct struct {
    factoryFunction func() myInterface
}

func (s *structToConstruct) performAction() {
    instance := s.factoryFunction()
    instance.DoSomething()
}

However, this code complains with the error:

.\main.go:6: cannot use expensive.CreateInstance (type func() *expensive.myStruct) as type func() myInterface in field value

However, *expensive.myStruct does implement the myInterface interface so I do not understand why Go is complaining about the type safety of this setup.

I've since realised after @jmaloney guideance that I could just wrapper my function like this in my main method:

    wrapper := func() myInterface {
        return expensive.CreateInstance()
    }
    thing := structToConstruct{wrapper}

and this then works but I still don't really understand why I can't use a struct that implements an interface when a function is expecting an instance of that interface to be returned especially when no type assertion/conversion is necessary on this fix as it is simply just calling the underlying factory function.

EDIT: I've since come across this proposal to add this to the language. The proposal was rejected:

https://github.com/golang/go/issues/12754

like image 514
Iain Duncan Avatar asked Feb 17 '17 17:02

Iain Duncan


People also ask

What are interfaces in Go programming?

Go - Interfaces. Go programming provides another data type called interfaces which represents a set of method signatures. The struct data type implements these interfaces to have method definitions for the method signature of the interfaces.

What is struct data type in Go programming?

Go programming provides another data type called interfaces which represents a set of method signatures. The struct data type implements these interfaces to have method definitions for the method signature of the interfaces.

Why do we return structs instead of interfaces?

This is the main motivation behind returning structs instead of interfaces: it leaves it up to the caller if they want to use it as the interface or if they want to use it as the struct, and in the end that makes a lot of sense. When to return interfaces? Let's expand a bit on our previous Person implementations.

Is it possible to test a struct with a factory function?

I have a struct in a package that has time consuming methods on it and is also time consuming to construct via it's factory function. Therefore, in the package that depends on this other struct I would like to be able to test it using both a fake factory function and a fake struct once it has been created.


1 Answers

getInstance needs to return myInterface

package main

import "fmt"

func main() {
    var function func() myInterface

    function = getInstance

    newSomething := function()

    newSomething.doSomething()
}

type myInterface interface {
    doSomething()
}

type myStruct struct{}

func (m *myStruct) doSomething() {
    fmt.Println("doing something")
}

func getInstance() myInterface {
    return &myStruct{}
}

Playground example

However, *expensive.myStruct does implement the myInterface interface so I do not understand why Go is complaining about the type safety of this setup.

In that instance you were not dealing with Go's interfaces you were dealing with the type signature of your struct.

when you first declared your struct with factoryFunction func() *myFunction factoryFunction now always needs to match the declared signature.

like image 117
jmaloney Avatar answered Oct 23 '22 05:10

jmaloney