Menu
Coddy logo textTech

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 icon

Challenge

Easy

Let'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 a BankAccount struct in the main package that models a real bank account with proper data protection:
    • HolderName (exported) - the account holder's name, safe to display publicly
    • AccountType (exported) - the type of account (e.g., "Savings", "Checking")
    • balance (unexported) - the actual balance, which should be protected
    • pin (unexported) - the account PIN, highly sensitive data
    Add a constructor function NewBankAccount that takes holder name, account type, initial balance, and PIN, then returns a pointer to a new BankAccount. Also add a method GetPublicInfo() string that returns the format: Account: [HolderName] ([AccountType])
  • main.go: Read account details from input, create a BankAccount using 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 (balance and pin) 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: Savings

Notice 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
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming