Start learning free

LESSON 185 OF 338 · OBJECT-ORIENTED PROGRAMMING

Access Modifiers in Go: Exported and Unexported

Lesson Read the full explanation

TL ; DR:

  • Go controls access through identifier capitalization. Lowercase names are unexported (package-private); uppercase names are exported (publicly visible). There are no public/private keywords.


  • Unexported fields are hidden from code outside the package:

    type Dog struct {
        name string   // unexported — hidden outside the package
    }
    
    func NewDog(name string) Dog { return Dog{name: name} }
    func (d Dog) getName() string { return d.name }
    

  • Outside the package, rex.name is a compile error. Use rex.getName() instead.



Full lesson:

Access control in Go uses identifier capitalization instead of explicit access keywords — lowercase identifiers are package-private (unexported), uppercase identifiers are public (exported).


type BankAccount struct {
    balance float64   // unexported — hidden outside the package
}

func (b *BankAccount) deposit(amount float64) {
    if amount < 0 { return }
    b.balance += amount
}
func (b BankAccount) getBalance() float64 { return b.balance }

If balance were exported (written as Balance), any client could just write account.Balance = -1000 and corrupt the state.


Your task

The Dog struct has its name field unexported (lowercase). Add a getName() method that returns name.

Dog's name: Rex
STEP 1 OF 4
    Ctrl + Enter to check

    GO · OBJECT-ORIENTED PROGRAMMING

    Access Modifiers in Go, explained

    Visibility is decided by capitalisation

    Go has no public, private or protected keywords. Whether a name can be used from other packages depends on its first letter:

    • A name that starts with an uppercase letter is exported: code in any package can use it.
    • A name that starts with a lowercase letter is unexported: only code in the same package can use it.

    The rule applies to every package-level name: types, functions, variables, constants, and also the fields and methods of a struct. So Dog, NewDog and a field Name are public, while dog, newDog and a field name are private to the package.

    Note the unit of privacy: it is the package (a directory of files compiled together), not the type and not the file. There is nothing like a Java private field that other code in the same package cannot touch. Code anywhere in the package can read and write an unexported field.

    Why hide a field?

    Hiding data protects the rules of a type. Take a bank account:

    type BankAccount struct {
        balance float64 // unexported
    }
    
    func (b *BankAccount) deposit(amount float64) {
        if amount < 0 {
            return
        }
        b.balance += amount
    }
    
    func (b BankAccount) getBalance() float64 { return b.balance }
    

    If balance were exported as Balance, any client could write account.Balance = -1000 and bypass the check in deposit. With the field unexported, code outside the package has to go through the methods. The methods keep the object in a valid state, and the internals can change later without breaking clients.

    What it looks like across packages

    Suppose Dog lives in a package named kennel, with an unexported name field. Code in another package, such as main, sees this:

    rex := kennel.NewDog("Rex")
    fmt.Println(rex.name)      // compile error: name is unexported
    fmt.Println(rex.GetName()) // works, if GetName is exported
    

    The lessons run as one package, so there you cannot see the error. Everything shares the package, and rex.name would compile. The exercise therefore practises the pattern rather than the compile error: expose a field through a method.

    Exported methods and constructors

    The same rule explains the constructor convention. NewDog starts with an uppercase letter so other packages can call it, while the struct's fields stay lowercase. Outside code builds a Dog through the constructor and reads it through exported methods, and it never touches the fields directly. This is how Go achieves encapsulation without keywords.

    Getter naming

    This lesson names its getter getName. In ordinary Go code a getter for a field name is called Name, without the get prefix, and a setter is SetName. Because the first letter is uppercase, that getter is also exported, which is the point if other packages are meant to call it. A method named getName is itself unexported, so in a real multi-package program no outside code could call it. Inside this lesson's single package, it works.

    The lesson's exercise

    The Dog struct has an unexported name field and a NewDog constructor. The task is a getter and a short program that uses it.

    1. Declare getName on a Dog receiver named this, returning a string.
    2. Return this.name. The method belongs to Dog, so it may read the field.
    3. Declare mainFunction with no parameters.
    4. Create rex with NewDog("Rex"), so the name is set in one place, and print the label joined to rex.getName() with +, which concatenates strings.
    type Dog struct {
        name string
    }
    
    func NewDog(name string) Dog {
        return Dog{name: name}
    }
    
    func (this Dog) getName() string {
        return this.name
    }
    
    
    
    func mainFunction() {
        rex := NewDog("Rex")
        fmt.Println("Dog's name: " + rex.getName())
    }
    

    The output is:

    Dog's name: Rex
    

    Common mistakes

    • Looking for a keyword. Writing private name string is a syntax error. Capitalisation is the only switch.
    • Exporting the field by accident. Capitalising it, as in Name, lets every package change it directly.
    • Assuming same-package code is blocked. Unexported means package-private, not type-private.
    • Mixing a string and a number with +. "Age: " + 4 does not compile. Use fmt.Sprintf or strconv.Itoa.
    • Forgetting the return type. A getter declared without string after its parentheses cannot return a value.

    Key takeaways

    • Uppercase first letter means exported, lowercase means package-private.
    • This applies to types, functions, fields and methods.
    • Unexported fields plus methods let a type enforce its own rules.
    • The usual Go getter is Name(), not getName().
    Keep going with GoAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free