Go > Packages and Modules > Creating Packages > Exported identifiers

Exported Identifiers in Go Packages

This snippet demonstrates how to create and use exported identifiers (variables, functions, types) in Go packages. Exported identifiers are accessible from outside the package, while unexported ones are only accessible within the package.

Creating a Go Package

This code defines a package named `mypackage`. It includes an exported variable `ExportedVariable`, an unexported variable `unexportedVariable`, an exported function `ExportedFunction`, and an unexported function `unexportedFunction`. It also includes an exported struct `ExportedStruct` with an exported field `ExportedField` and an unexported field `unexportedField`. The function `NewExportedStruct` is a common pattern to initialize struct and return it.

// mypackage/mypackage.go
package mypackage

import "fmt"

// ExportedVariable is accessible from outside the package.
var ExportedVariable = "Hello from mypackage"

// unexportedVariable is only accessible within the package.
var unexportedVariable = "This is private"

// ExportedFunction is accessible from outside the package.
func ExportedFunction() {
	fmt.Println(ExportedVariable)
	fmt.Println(unexportedFunction())
}

// unexportedFunction is only accessible within the package.
func unexportedFunction() string {
	return unexportedVariable
}

// ExportedStruct is accessible from outside the package.
type ExportedStruct struct {
	// ExportedField is accessible from outside the package.
	ExportedField string
	// unexportedField is only accessible within the package.
	unexportedField string
}

// NewExportedStruct is a constructor function for ExportedStruct.
func NewExportedStruct(exported string, unexported string) *ExportedStruct {
	return &ExportedStruct{ExportedField: exported, unexportedField: unexported}
}

// ExportedMethod is accessible from outside the package.
func (e *ExportedStruct) ExportedMethod() string {
	return e.ExportedField
}

Using the Package

This code imports the `mypackage` package and uses its exported identifiers. Note that you can access `ExportedVariable`, `ExportedFunction`, `ExportedStruct` and `ExportedField` because they are exported (start with a capital letter). Attempting to access `unexportedVariable` or `unexportedField` directly will result in a compile-time error because they are unexported.

// main.go
package main

import (
	"fmt"
	"mypackage"
)

func main() {
	fmt.Println(mypackage.ExportedVariable)
	mypackage.ExportedFunction()

	myStruct := mypackage.NewExportedStruct("Public Value", "Private Value")
	fmt.Println(myStruct.ExportedField)
	fmt.Println(myStruct.ExportedMethod())
	//fmt.Println(myStruct.unexportedField) // This will cause a compile error
}

Concepts Behind Exported Identifiers

In Go, visibility is controlled by the case of the first letter of an identifier (variable, function, type, constant, field or method name). Identifiers that start with an uppercase letter are exported, meaning they are accessible from other packages. Identifiers that start with a lowercase letter are unexported, meaning they are only accessible within the package where they are defined. This is Go's mechanism for encapsulation and information hiding.

Real-Life Use Case

Consider a library for handling HTTP requests. You might export functions like `Get`, `Post`, `Put`, and `Delete`, along with types like `Request` and `Response`. You would likely keep internal details about request creation and connection management unexported. This prevents users from directly manipulating internal state and allows you to refactor the library's internals without breaking external code. Using exported identifiers helps to create well-defined and stable APIs for your package.

Best Practices

  • Export only what is necessary: Avoid exporting identifiers that are not intended for external use. This reduces the API surface area of your package and makes it easier to maintain.
  • Use descriptive names: Choose clear and descriptive names for your exported identifiers. This makes your code easier to understand and use.
  • Provide constructor functions: For structs, provide constructor functions (like `NewExportedStruct` in the example) to ensure proper initialization and control over internal state.
  • Consider interfaces for abstraction: Use interfaces to define contracts for your exported types. This allows you to change the underlying implementation without affecting clients that use the interface.

Interview Tip

Be prepared to explain the difference between exported and unexported identifiers in Go. Also, be prepared to discuss why you would choose to export or unexport a particular identifier. A good answer will demonstrate an understanding of encapsulation, API design, and maintainability.

When to use them

Use exported identifiers when you want to provide functionality to other packages. Unexported identifiers should be used for internal implementation details that should not be directly accessed or modified by external code. It's a core concept of encapsulation in Go.

Alternatives

There aren't really alternatives to exported identifiers, as they are fundamental to Go's package system and visibility control. The choice is always whether a given identifier should be exported or unexported, depending on your package's design goals. You can use interfaces to provide a more abstract layer on exported types.

Pros

  • Encapsulation: Prevents external code from directly accessing and modifying internal state.
  • API Design: Allows you to create well-defined and stable APIs for your packages.
  • Maintainability: Makes it easier to refactor your code without breaking external code.
  • Reduced Coupling: Reduces the coupling between packages by limiting the direct dependencies.

Cons

  • Increased boilerplate: Sometimes requires more code to access internal state through exported methods or functions.
  • Potential for confusion: Can be confusing for beginners who are not familiar with the concept of exported identifiers.

FAQ

  • What happens if I try to access an unexported identifier from another package?

    You will get a compile-time error. The compiler will report that the identifier is not defined or not exported.
  • Can I change an unexported identifier to an exported identifier after the package is already in use?

    Yes, but this is considered a breaking change. Clients of your package will now be able to access the identifier, which might introduce unintended dependencies or behavior. It's generally best to avoid making such changes unless absolutely necessary, and to communicate the change clearly to users of your package.
  • Are constants also subject to export rules?

    Yes, constants follow the same export rules as variables, functions, and types. Constants that start with an uppercase letter are exported, and constants that start with a lowercase letter are unexported.