1

I have a singleton in my application, but it's published not as a struct directly, but as an interface (because I want to be able to dynamically select the particular implementation upon singleton initialization). Here's the code:

var once sync.Once
var instance defaultConfiguration

type Configuration interface {
    GetFoo() string
}

type defaultConfiguration struct {
}

func (dc defaultConfiguration) GetFoo() string {
    return "foo"
}

func NewConfiguration() Configuration {
    once.Do(func() {
        instance = defaultConfiguration{}
    })
    return instance
}

Then I decided to write a unit-test that would check that NewConfiguration() will actually return the same instance each time:

func TestNewConfigurationSameInstance(t *testing.T) {
    configuration1 := NewConfiguration()
    configuration2 := NewConfiguration()
    if &configuration1 != &configuration2 {
        t.Error()
    }
}

I thought it would make sense to compare the addresses of the returned instances, however, this test fails.

Then I thought, well, maybe I have to return a pointer to an instance, so I've changed the code to look like this:

func NewConfiguration() *Configuration {
    once.Do(func() {
        instance = defaultConfiguration{}
    })
    return &instance
}

But this doesn't even compile: it fails with the error message

cannot use &instance (type *defaultConfiguration) as type *Configuration in return argument: *Configuration is pointer to interface, not interface

And I've got very confused. Why can't I return a pointer to an interface? Or, why returning defaultConfiguration as Configuration is valid, but returning *defaultConfiguration as *Configuration is not?

And, after all, what is the proper unit-test for my use-case?

Dmytro Titov
  • 2,802
  • 6
  • 38
  • 60

1 Answers1

2

Your code should be:

var once sync.Once
var instance *defaultConfiguration

type Configuration interface {
    GetFoo() string
}

type defaultConfiguration struct {
}

func (dc *defaultConfiguration) GetFoo() string {
    return "foo"
}

func NewConfiguration() Configuration {
    once.Do(func() {
        instance = &defaultConfiguration{}
    })
    return instance
}

Since Configuration is an interface and you want a pointer to defaultConfiguration to implement it.

Pointers to interfaces (e.g. *Configuration) are rarely needed. An interface is already a reference value, and it's perfectly fine for a pointer to some type to implement an interface.

For more background on the root issue read this answer or similar resources.

Eli Bendersky
  • 263,248
  • 89
  • 350
  • 412
  • If I do as suggested, it compiles, but the unit-test still fails, because `&configuration1` and `&configuration2` are different. – Dmytro Titov Apr 21 '20 at 13:23
  • @DmytroTitov: can you update your question with the minimal code that causes the issue now? – Eli Bendersky Apr 26 '20 at 15:15
  • What do you mean? I just took your code snippet instead of mine, and then I ran the unit-test from my original post. It failed. – Dmytro Titov Apr 27 '20 at 09:39
  • @DmytroTitov: see https://play.golang.org/p/tbbzmdYO1EY and also read https://golang.org/ref/spec#Comparison_operators -- comparing pointers and pointers to pointers is different – Eli Bendersky Apr 27 '20 at 16:48