Exported vs Unexported Fields
Part of the Object Oriented Programming section of Coddy's GO journey — lesson 47 of 107.
In Go, encapsulation at the struct level is controlled by the case of field names. This simple rule determines whether fields can be accessed from outside the package where the struct is defined.
Fields starting with an uppercase letter are exported—accessible from any package. Fields starting with a lowercase letter are unexported—only accessible within the same package:
// In package "user"
type User struct {
Name string // Exported - accessible everywhere
Email string // Exported - accessible everywhere
password string // Unexported - only accessible in "user" package
age int // Unexported - only accessible in "user" package
}
When another package imports and uses this struct, it can only interact with exported fields:
// In package "main"
import "user"
func main() {
u := user.User{
Name: "Alice", // works
Email: "a@mail.com", // works
// password: "secret", // compile error: unknown field
}
fmt.Println(u.Name) // works
// fmt.Println(u.password) // compile error: cannot refer to unexported field
}
This mechanism protects sensitive data and internal implementation details. The password field cannot be read or modified directly from outside the package, forcing other code to use methods you provide. This is Go's approach to achieving data hiding—a core principle of encapsulation in object-oriented programming.
Challenge
EasyLet's build a bank account system that demonstrates encapsulation through exported and unexported fields. You'll create a package that protects sensitive financial data while exposing safe public information.
You'll organize your code across two files:
account.go: Create aBankAccountstruct in themainpackage that models a real bank account with proper data protection:HolderName(exported) - the account holder's name, safe to display publiclyAccountType(exported) - the type of account (e.g., "Savings", "Checking")balance(unexported) - the actual balance, which should be protectedpin(unexported) - the account PIN, highly sensitive data
NewBankAccountthat takes holder name, account type, initial balance, and PIN, then returns a pointer to a newBankAccount. Also add a methodGetPublicInfo() stringthat returns the format:Account: [HolderName] ([AccountType])main.go: Read account details from input, create aBankAccountusing the constructor, and demonstrate the encapsulation. Print the public info using the method, then directly access and print each exported field on separate lines. The unexported fields (balanceandpin) are protected—you can only set them through the constructor.
The following inputs will be provided:
- Line 1: Account holder name
- Line 2: Account type
- Line 3: Initial balance (as a float)
- Line 4: PIN code
For example, given Alice Johnson, Savings, 5000.50, and 1234, your output should be:
Account: Alice Johnson (Savings)
Holder: Alice Johnson
Type: SavingsNotice that the balance and PIN never appear in the output—they're safely hidden inside the struct, accessible only within the package. This is encapsulation in action: sensitive data is protected while public information remains accessible.
Cheat sheet
In Go, encapsulation is controlled by the case of field names in structs:
- Uppercase fields are exported (accessible from any package)
- Lowercase fields are unexported (only accessible within the same package)
type User struct {
Name string // Exported - accessible everywhere
Email string // Exported - accessible everywhere
password string // Unexported - only accessible in same package
age int // Unexported - only accessible in same package
}
When using a struct from another package, only exported fields are accessible:
import "user"
func main() {
u := user.User{
Name: "Alice", // works
Email: "a@mail.com", // works
// password: "secret", // compile error: unknown field
}
fmt.Println(u.Name) // works
// fmt.Println(u.password) // compile error: cannot refer to unexported field
}
This mechanism protects sensitive data and internal implementation details, forcing external code to use provided methods for access.
Try it yourself
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Read account holder name
holderName, _ := reader.ReadString('\n')
holderName = strings.TrimSpace(holderName)
// Read account type
accountType, _ := reader.ReadString('\n')
accountType = strings.TrimSpace(accountType)
// Read initial balance
balanceStr, _ := reader.ReadString('\n')
balanceStr = strings.TrimSpace(balanceStr)
balance, _ := strconv.ParseFloat(balanceStr, 64)
// Read PIN
pinStr, _ := reader.ReadString('\n')
pin := strings.TrimSpace(pinStr)
// TODO: Create a new BankAccount using the constructor
// account := NewBankAccount(...)
// TODO: Print the public info using the GetPublicInfo() method
// TODO: Print the holder name by directly accessing the exported field
// fmt.Println("Holder:", ...)
// TODO: Print the account type by directly accessing the exported field
// fmt.Println("Type:", ...)
// Note: balance and pin are unexported - they cannot be accessed directly here
// They are safely encapsulated within the struct
_ = balance // Remove this line when you use balance
_ = pin // Remove this line when you use pin
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of Go OOP
External FilesGo Workspace & ModulesPackages & ImportsExported vs Unexported NamesIntroduction to OOP in GoStructs as ClassesDefining Methods on StructsPointer vs Value ReceiversStruct InitializationConstructor FunctionsRecap - Simple Calculator4Interfaces
Introduction to InterfacesImplicit ImplementationInterface as ContractEmpty Interface (any)Type AssertionType SwitchInterface CompositionStringer & Error InterfacesRecap - Shape Calculator7Encapsulation
Exported vs Unexported FieldsPackage-Level EncapsulationGetter & Setter MethodsInformation Hiding in GoRecap - Student Records10Generics (Go 1.18+)
Introduction to GenericsType ParametersType ConstraintsGeneric StructsGeneric Methods WorkaroundRecap - Generic Collection2Types & Structs Deep Dive
Basic & Composite TypesCustom Type DefinitionsStruct TagsAnonymous StructsNested StructsZero Values & DefaultsRecap - Contact Book5Composition Over Inheritance
Why Go Has No InheritanceStruct Embedding BasicsMethod PromotionEmbedding Multiple StructsEmbedding vs AggregationShadowing Embedded MethodsRecap - Employee Hierarchy8Error Handling & OOP
The error InterfaceCustom Error TypesError Wrapping (fmt.Errorf)Sentinel Errorserrors.Is() and errors.As()Panic, Defer, and RecoverRecap - File Parser3Pointers & Memory
Pointer Basics in GoPointers to StructsPass by Value vs ReferenceThe new() FunctionGarbage Collection in GoRecap - Linked List Builder6Polymorphism in Go
Polymorphism via InterfacesDuck Typing in GoInterface Satisfaction RulesPolymorphic CollectionsDependency InjectionRecap - Payment Processor9Concurrency & OOP
Goroutines BasicsChannels & CommunicationBuffered vs Unbuffered ChanSelect Statementsync.Mutex & sync.RWMutexsync.WaitGroupThread-Safe Struct DesignRecap - Worker Pool