Singleton and "Exported function with the unexported return type"

I am currently looking through some patterns and how they can be implemented in Go, using the following site : Design Patterns in Go

If I want to create the Singleton pattern, but inside a package, accessed from another package, then I will get a warning from the linter : Exported function with the unexported return type. This is because the struct single is not exported.

I read on https://www.reddit.com/r/golang/comments/xurmnl/return_unexported_type/ that most people seem to think that this is a bad idea, for example, one person wrote :

You can go for an unexported type, but that makes things quite awkward for callers because they get a value that's difficult to pass around, since the type cannot be referred to.

In the case of a Singleton you should never actually need to pass around the object though, you can just call GetInstance() again in the function that needs the object.

Now to my question : Is the Singleton pattern (inside a package) a case where it is actually correct/accepted to use an exported function with the unexported return type? Or is there a better Go-way of creating a Singleton (inside a package)?

Example using the init function (for sync.Mutex and sync.Once variations, see the first link):

package single

import "fmt"

type single struct {
}

var singleInstance *single

func GetInstance() *single {   // <---- Warning here
	fmt.Println("Single instance already created.")
	return singleInstance
}

func init() {
	fmt.Println("Creating single instance now.")
	singleInstance = &single{}
}

Hello there. I use this web site with patterns too. But point your attention that the code provided there is the Conceptual Example. This is how you can write your own singleton struct in go, but it does not mean it will be exactly the way it’s shown there. Export of the variables from the packages is the decision of the developer. If any of the types, variables, functions, interfaces etc, you declared, need to be used outside the package, then they should be exported. Unexported stuff is mostly used to hide unnecessary details of the process, wrap functions e.g. different architectures or store internal values, so no one can change them besides your code. In your case, if the singleton of this struct is needed outside the package, just make it exported.

I understand unexported vs exported. My question is more about the warning and people’s comments in the reddit-thread.

If I create a Singleton package (for some unspecified reason, for example a database connection), then making the Single struct exported would mean that anyone could instantiate that type whenever they wanted, rendering the Singleton pattern useless in this case.

So, my question basically is, is the Singleton pattern a case where exporting an unexported struct actually is necessary? People in the Reddit-thread seemed very sure that it is a bad idea, but I don’t see how I can implement a Singleton without leaving the single struct unexported.

In your case, if the singleton of this struct is needed outside the package, just make it exported.

A Singleton should never be instantiated randomly by other people, that’s the whole purpose of the pattern. It should be created once, and then be reused every time someone asks for it. So making it exported makes no sense to me.

Hopefully this explains my question a little bit better…thanx for trying to make sense of my question btw :slight_smile:

Ps. I guess part of the problem is that I hate having warnings in my code. I feel like I am doing something wrong when there are warnings. But in this case, I don’t see how I can create a true Singleton without this warning.

Personally I would agree with reddit. I would say it’s something to avoid the possible confusion in future. Another way of solving it, is at least to return exported interface, so you can understand what it is used for and why.

Ps. What linter shows you a problem with this type? My vs code is silent and golangci-lint as well

I am not sure if linter is the correct word here, I get a warning in Jetbrains GoLand IDE:

I might need to investigate if that is a warning from the IDE and not from Go…

It might be the IDE indeed. I’ve also found this discussion, if you haven’t yet read it

1 Like

Thank you for that link, that discussion seems to solve the issue for me. Using an interface instead seems to be what people recommend:

func GetInstance() Instance { ... }

type Instance interface {
    Hello()
    Goodbye()

    private() // This private method prevents others from implementing this interface
}

Thanks again…

1 Like

And yes, it was the IDE that gave me that warning:

I stand by my personal opinion that Goland encourages bad Go.

Also, why do you need a singleton? There may be a more idiomatic way to do whatever you actually want to do.

I don’t need a Singleton, I am just going trough some patterns, see the first message.

Interesting opinion, I have never heard that one before. Care to elaborate?

Personally, I love Goland…