Don't Export Private Types in Go

In Go development, a common design mistake is the export of private types. It may look insignificant at first, but in reality, it breaks encapsulation and complicates project maintenance. In this article, we’ll discuss why this anti-pattern appeared in the first place, why it’s problematic, and what practical consequences it has for code architecture.
May 28, 2024 — Estimated reading time: 8 minutes

Unexported Types in Go: Where It All Started

When transitioning to the world of Go from classical object-oriented languages like Java or PHP, many developers are accustomed to using classes and automatic object initialization when calling constructors.

However, structures in Go are not the same as classes and objects in other languages. They play a similar role, but not identical!
Learn more about our approach to web development, and we also recommend checking out our cases.

Problems in Exporting Private Types

The main issue with exporting private types is that it violates the principle of encapsulation.

The very term "unexported structure" suggests that it is for internal use within the package. It's needed so that code consumers work ONLY with exported types. This encapsulation allows hiding what doesn't need to be accessed externally! Elements that work in a complex manner and can change with new package versions. Therefore, doing this is akin to trying to make something labeled "inedible" edible. It's like hammering nails with a drill.

Writing Structures That Don't Require Initialization

Examples: sync.Mutex, time.Time, sync.WaitGroup, and others.

These will work correctly with default initialization. Named constructors can be used to initialize them to the desired state: time.Now(), time.Parse(). This is the ideal approach. However, this cannot be achieved in all cases.

Complex Domain Services May Require Deeper Initialization

For example, dependency injection (Config, Logger, Repo, NetClient, etc.). In this case, a named constructor and a structure with private fields are created, into which nothing can be written at the basic initialization. In this case, dependencies, configurations, and other aspects are checked in the constructor, which may return an error.

NewDomainService(c Config, d Dependencies) (*DomainService, error)

Such services always have methods in their signature that perform some domain function:

  • Start(ctx context.Context) error
  • Process(ctx context.Context) error
  • Execute(ctx context.Context) error
  • .....

If the service has not been properly initialized, they will return an error. In 99% of cases, such services can return various errors for dozens of reasons. So the argument "I'm too lazy to handle errors" is simply ineffective.

Handling Errors

This is done with just two lines, automatically generated by the IDE. And this allows you to immediately understand the weak points of your code, potential errors, and prompts you to think about what to do with them. Don’t worry, nothing bad will happen.

The consumer will handle it, and in case of incorrect initialization, the programmer will see an error at the module's test level, if, of course, they write them. In the worst case, everything will collapse when trying to run the application in the very first test run.

However, if such a problem (my service was not initialized) leads to "some scary critical things," such as the database being deleted, the disk being formatted, or bitcoins being transferred to a non-existent wallet. The problem is with you, not with whoever is using your code! Because such things should never happen!

If you guard against unknown issues with "naive consumers"; expect problems.

Namely:

  1. You won't be able to build a proper application architecture. Because you simply can't use this private type in the top-level layers. For example, if it's a repository or some other domain service, it needs to be described in the consumer's interface. Keep in mind that you cannot do that because it's a private type! You simply won't be able to type anything beyond its package. Therefore, DI won't work.
  2. If you want to pass it to the interface type of some consumer, you'll need the following:

    - ServiceLocator or ApplicationRegistry – this is a top-level architecture object that stores references to the main services. It must know how to store the initialization of this object.

    - IoC – here you will need to bind the implementation for the interface, which will be automatically injected during object building. Whether you do it manually, use Wire or FX, you still need to write, for example, an FX Show constructor for this type, and then bind it to the necessary interfaces. All this is impossible to do with private types.
  3. You won't be able to use GoDoc documentation because private types are not described there for external use. which is absolutely logical!
  4. 4. It contradicts the language's idiomatics and confuses other developers with a non-standard and incorrect approach.

Conclusion

Exporting private types in Go often looks like a quick fix but actually creates challenges for maintaining and scaling code. Keep private types strictly within the package and follow the standards established by the Go community.

If you’d like to refine your project architecture or get expert consultation on Go development — reach out to us. We’ll recommend reliable, practical solutions tailored to your needs.

More articles by this author

SEO Is an Investment — Here's What It Really Returns SEO Is an Investment — Here's What It Really Returns

In Russia and the CIS, SEO is an effective way to grow your business online. As advertising costs continue to rise, organic promotion...

May 2, 2025
Sitemap.xml Mistakes That Are Killing Your SEO Traffic Sitemap.xml Mistakes That Are Killing Your SEO Traffic

A sitemap (Sitemap.xml) is like a navigation system for search engines. When set up correctly, it helps your website grow faster in s...

May 2, 2025
"Just Add a Block"? Why It Doesn’t Cost $10 or Take 5 Minutes "Just Add a Block"? Why It Doesn’t Cost $10 or Take 5 Minutes

You go to a developer and say, “I just need a small block added — just a few lines of text. What’s the big deal?” It sounds like a qu...

April 3, 2025
Fixed Price vs. Hourly Rate: Which Payment Model is Right for Your Business? Fixed Price vs. Hourly Rate: Which Payment Model is Right for Your Business?

You start a project, and the contractor suggests an hourly rate. You, however, prefer a fixed price — a clear total with no surprises...

April 1, 2025

Similar Articles

Web Application Security - A Modern Approach Web Application Security - A Modern Approach

In modern web portal development, security has become a top priority. Today, protecting user data and ensuring the resilience of web...

May 28, 2024
Wie wählt man eine Domain für die Website aus Wie wählt man eine Domain für die Website aus

Die Wahl einer Domain ist ein wichtiger erster Schritt bei der Webentwicklung, und sie sollte sofort richtig gemacht werden. Wussten Sie, dass es notwendig ist,...

April 18, 2024
Efficient Website Management: How Business Automation Boosts Profits and Saves Time on Routine Tasks and Accounting Issues Efficient Website Management: How Business Automation Boosts Profits and Saves Time on Routine Tasks and Accounting Issues

How does business automation affect website management, efficiency, and company profit? Automating routine processes and customer man...

February 2, 2024
The Dark Side of Content Management The Dark Side of Content Management

What is content management? Simply put, it’s the systematic development of a website — from planning publications to filling it with u...

November 19, 2024
cookies We use Cookies

We use cookies to improve website performance, personalize content, and analyze traffic. You can choose which categories of cookies to allow. For more information, please see our Cookie Policy. You can change your preferences at any time.

Essential (Required)

Ensure the website functions properly (navigation, access to secure areas). Always enabled and can only be changed in your browser settings.

Analytics

Help us understand how you use the website so we can improve our services. Do not collect personal data. We use several analytics tools for this purpose.

Advertising

Used to deliver personalized ads and measure the effectiveness of advertising campaigns.