See this experiment.
~/go/src$ tree -F . ├── 1-foodir/ │ └── 2-foofile.go └── demo.go 1 directory, 2 files ~/go/src$ cat demo.go package main import ( "fmt" "1-foodir" ) func main() { fmt.Println(foopkg.FooFunc()) } ~/go/src$ cat 1-foodir/2-foofile.go package foopkg func FooFunc() string { return "FooFunc" } ~/go/src$ GOPATH=~/go go run demo.go FooFunc
I thought that we always import a package name. But the above example shows that we actually import a package directory name ("1-foodir"
) but while invoking exported names within that package, we use the package name declared in the Go files (foopkg.FooFunc
).
This is confusing for a beginner like me who comes from Java and Python world, where the directory name itself is the package name used to qualify modules/classes defined in the package.
Why is there a difference in the way we use import
statement and refer to names defined in the package in Go? Can you explain the rules behind these things about Go?
The package Directories provides operations for manipulating files and directories, and their names.
Go first searches for package directory inside GOROOT/src directory and if it doesn't find the package, then it looks for GOPATH/src . Since, fmt package is part of Go's standard library which is located in GOROOT/src , it is imported from there.
The package “main” tells the Go compiler that the package should compile as an executable program instead of a shared library. The main function in the package “main” will be the entry point of our executable program.
Go programs are organized into packages. A package is a collection of source files in the same directory that are compiled together. Functions, types, variables, and constants defined in one source file are visible to all other source files within the same package. A repository contains one or more modules.
If what you said was true, then your function call would actually be 1-foodir.FooFunc()
instead of foopkg.FooFunc()
. Instead, go sees the package name in 2-foofile.go and imports it as foopkg
because in go the name of the package is exactly what comes after the words package
at the top of .go files, provided it is a valid identifier.
The only use of the directory is for collecting a set of files that share the same package name. This is reiterated in the spec
A set of files sharing the same PackageName form the implementation of a package. An implementation may require that all source files for a package inhabit the same directory.
In go, it is convention that the directory match the package name but this doesn't have to be the case, and often it is not with 3rd party packages. The stdlib does do a good job of sticking to this convention.
Now where directories do come into play is the import path. You could have 2 packages named 'foo' in your single binary as long as they had different import paths, i.e.
/some/path/1/foo
and /some/path/2/foo
And we can get really swanky and alias the imports to whatever we wanted, for example I could do
import ( bar "/some/path/1/foo" baz "/some/path/2/foo" )
Again the reason this works is not because the package name has to be unique, but the package import path must be unique.
Another bit of insight to glean from this statement is -- within a directory, you cannot have two package names. The go compiler will throw an error stating it cannot load package
and that it found packages foo (foo.go) and bar (bar.go)
.
See https://golang.org/doc/code.html#PackageNames for more information.
Roughly for the why:
Packages have a "name" which is set by the package clause, the package thepackagename
at the start of your source code.
Importing packages happens by pretty much opaque strings: the import path in the import declarations.
The first is the name and the second how to find that name. The first is for the programmer, the second for the compiler / the toolchain. It is very convenient (for compilers and programmers) to state
Please import the package found in
"/some/hierarchical/location"
and then refer to that package by it's simple name like robot
in statements like
robot.MoveTo(3,7)
Note that using this package like
/some/hierarchical/location.MoveTo(3.7)
would not be legal code and neither readable nor clear nor convenient. But to for the compiler / the toolchain it is nice if the import path has structure and allows to express arbitrary package locations, i.e. not only locations in a filesystem, but e.g. inside an archive or on remote machines, or or or.
Also important to note in all this: There is the Go compiler and the go
tool. The Go compiler and the go tool are different things and the go tool imposes more restrictions on how you lay out your code, your workspace and your packages than what the Go compiler and the language spec would require. (E.g. the Go compiler allows to compile files from different directories into one package without any problems.)
The go
tool mandates that all (there are special cases, I know) your source files of a package reside in one file system directory and common sense mandates that this directory should be "named like the package".
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With