Exported identifiers in Go

Michał Łowicki
golangspec
Published in
3 min readAug 21, 2016

--

Package consists of source files within single directory. Inside such directory it’s illegal to have files from different packages. Package statement which begins each source file in Go defines the package to which file belongs:

package foo

Package statement is not a declaration introducing new identifier so “foo” cannot be used later in the source file.

Name of the package has syntax like regular identifier. All files sharing the same package name form the package.

In order to use identifiers from other package(s) an import declaration is needed:

import “fmt”

String which is specified after import keyword is called import path. It needs to uniquely identify a package. Packages from standard library use short import paths but normally it’s something longer like “github.com/mlowicki/foo”.

In form above access to exported identifiers is done via name of package from package statement. So besides identifying files belonging to package it also servers as a default package name for import declaration. It can be overridden though by identifier preceding import path:

import (
f “fmt”
)
func main() {
f.Println(“whatever”)
}

As described in “Scopes in Go” the scope of the package name is the file block.

Not all package’s identifiers are accessible after import. Only exported identifiers are and they’re two rules identifier must adhere to in order to be directly accessible from other package:

  • the first character of identifier is an upper case letter
  • either identifier is defined in package block or it’s a field name or method name

Identifiers from package block

Being defined in a package block means it’s defined outside of any function like:

package libraryvar V = 1type S struct {
Name string
}
type I interface {
M()
}

V, S and I can be used in files with proper import statement:

package mainimport (
“fmt”
“github.com/mlowicki/library”
)
func main() {
s := library.S{}
fmt.Println(library.V, s)
}

Exported field name

Field name must also start with an upper case letter to be accessible from other package:

package librarytype record struct {
Name string
age int8
}
func GetRecord() record {
return record{Name: “Michał”, age: 29}
}
package mainimport (
“fmt”
“github.com/mlowicki/library”
)
func main() {
record := library.GetRecord()
fmt.Println(record.Name)
}

The code above works fine but attempt to access not exported field age

fmt.Println(record.age)

fails at compilation time:

record.age undefined (cannot refer to unexported field or method age)

Making struct in library package exportable so renaming to Record doesn’t change anything — still age field won’t be exported even if struct type will.

Exported method name

The same rules as with field names apply to methods:

package libraryimport “fmt”type Duck interface {
Quack()
walk()
}
type Record struct{}func (Record) Quack() {
fmt.Println(“Quack”)
}
func (Record) walk() {
fmt.Println(“walk”)
}
func GetDuck() Duck {
return Record{}
}
package mainimport (
“github.com/mlowicki/library”
)
func main() {
duck := library.GetDuck()
duck.Quack()
record := library.Record{}
record.Quack()
}

outputs:

> ./bin/sandbox
Quack
Quack

It’s illegal to call method walk:

duck.walk()

as it gives:

duck.walk undefined (cannot refer to unexported field or method walk)

or:

record.walk()

which also fails while building with error message:

record.walk undefined (cannot refer to unexported field or method library.Record.””.walk)

Resources:

--

--

Michał Łowicki
golangspec

Software engineer at Datadog, previously at Facebook and Opera, never satisfied.