Relationship between a package statement and the directory of a .go file
Asked Answered
M

4

46

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?

Melaniamelanic answered 24/4, 2017 at 4:29 Comment(0)
C
37

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.

Consolatory answered 24/4, 2017 at 5:20 Comment(2)
There is no #PackageNames under ` golang.org/doc/code.html` any longer.Unrepair
Ah unfortunate. And it 301s to the new docs which have been completely redone. Thankfully we have the wonderful Internet Archive: web.archive.org/web/20190411104609/https://golang.org/doc/…Consolatory
B
11

Roughly for the why:

  1. Packages have a "name" which is set by the package clause, the package thepackagename at the start of your source code.

  2. 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".

Bismarck answered 24/4, 2017 at 7:10 Comment(1)
plus one for the bit about the go tools I missed that in my answer.Consolatory
S
8

First thing first, the package clause and import path are different things.

package clause declares PackageName:

PackageClause  = "package" PackageName .
PackageName    = identifier .

The purpose of a package clause is to group files:

A set of files sharing the same PackageName form the implementation of a package.

By convention, the path basename (directory name) of ImportPath (see below) is the same as PackageName. It's recommended for convenient purposes that you don't need to ponder what's the PackageName to be used.

However, they can be different.

The basename only affects the ImportPath, check spec for import declartions:

ImportDecl       = "import" ( ImportSpec | "(" { ImportSpec ";" } ")" ) .
ImportSpec       = [ "." | PackageName ] ImportPath .
ImportPath       = string_lit .

If the PackageName is omitted, it defaults to the identifier specified in the package clause of the imported package.

For example, if you have a dir foo but you declare package bar in a source file resides in it, when you import <prefix>/foo, you will use bar as a prefix to reference any exported symbols from that package.

darehas' answer raises a good point: you can't declare multiple packages under the same basename. However, according to package clause, you can spread the same package over different basenames:

An implementation may require that all source files for a package inhabit the same directory.

Stung answered 4/1, 2021 at 17:49 Comment(2)
I don’t understand your last sentence: If I move one file of a certain package into e.g. a subdirectory of the package directory, it does not compile any more. So what do you mean with that?Seaware
in this case, your implementation requires all sources of a package to be located under the same directory. Or, you may update the import path in your parent directory.Stung
C
-1

Rules

  • package xxx declare the package name, which is the default alias when import.
  • import path/to/dir will use the dir path, not necessarily package name.
  • 2 different dir can have the same package xxx.
    • But when import the 2 dir in the same go file, need to give them different aliases.
    • The 2 dir can even import & use each other.
  • If u use package __ (double underscore), then:
    • When import such package, must specify alias, can't omit, otherwise can't find the package.
    • When import, editor (e.g goland) can't auto search, must import by hand.

Example code

(The common base dir for files below is eric/go/pkg_learn/pkg_dir_diff)

Source file tree:

eric/go/pkg_learn/pkg_dir_diff
├── d
│   └── d.go
├── d1
│   └── d1.go
├── d2
│   └── d2.go
├── pkg_1_and_2_test.go
├── pkg_1_test.go
└── pkg_2_test.go
  • eric/go/pkg_learn/pkg_dir_diff/d/d.go

    package __
    
    func D() {
        println("d")
    }
    
  • eric/go/pkg_learn/pkg_dir_diff/d1/d1.go

    package d
    
    import d2 "eric/go/pkg_learn/pkg_dir_diff/d2"
    
    func One() {
        println("one")
        d2.Two()
    }
    
    func OneTwo() {
        One()
        d2.Two()
    }
    
  • eric/go/pkg_learn/pkg_dir_diff/d2/d2.go

    package d
    
    func Two() {
        println("two")
    }
    
  • eric/go/pkg_learn/pkg_dir_diff/pkg_1_test.go

    package pkg_learn
    
    import (
        d "eric/go/pkg_learn/pkg_dir_diff/d1"
        "testing"
    )
    
    func TestSamePkgFromDifferentDirs1(t *testing.T) {
        d.One()
    }
    
  • eric/go/pkg_learn/pkg_dir_diff/pkg_2_test.go

    package pkg_learn
    
    import (
        d "eric/go/pkg_learn/pkg_dir_diff/d2"
        "testing"
    )
    
    func TestSamePkgFromDifferentDirs2(t *testing.T) {
        d.Two()
    }
    
  • eric/go/pkg_learn/pkg_dir_diff/pkg_1_and_2_test.go

    package pkg_learn
    
    import (
        d "eric/go/pkg_learn/pkg_dir_diff/d"
        d1 "eric/go/pkg_learn/pkg_dir_diff/d1"
        d2 "eric/go/pkg_learn/pkg_dir_diff/d2"
        "testing"
    )
    
    func TestSamePkgFromDifferentDirs1and2(t *testing.T) {
        d1.One()
        d2.Two()
        d1.OneTwo()
    }
    
    func TestD(t *testing.T) {
        d.D()
    }
    
    
Casta answered 10/3, 2023 at 16:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.