How to use "internal" packages?

I try understand how to organize go code using "internal" packages. Let me show what the structure I have:

project/
  internal/
    foo/
      foo.go # package foo
    bar/
      bar.go # package bar
  main.go

# here is the code from main.go
package main

import (
  "project/internal/foo"
  "project/internal/bar"
)

project/ is outside from GOPATH tree. Whatever path I try to import from main.go nothing works, the only case working fine is import "./internal/foo|bar". I think I do something wrong or get "internal" package idea wrong in general. Could anybody make things clearer, please?

UPDATE

The example above is correct the only what I need was to place project/ folder under $GOPATH/src. So the thing is import path like the project/internal/foo|bar is workable if we only import it from project/ subtree and not from the outside.


Solution 1:

With modules introduction in Go v1.11 and above you don't have to specify your project path in $GOPATH/src

You need to tell Go about where each module located by creating go.mod file. Please refer to go help mod documentation.

Here is an example of how to do it:

project
|   go.mod
|   main.go
|
\---internal
    +---bar
    |       bar.go
    |       go.mod
    |
    \---foo
            foo.go
            go.mod

project/internal/bar/go.mod

module bar

go 1.14

project/internal/bar/bar.go

package bar

import "fmt"

//Bar prints "Hello from Bar"
func Bar() {
    fmt.Println("Hello from Bar")
}

project/internal/foo/go.mod

module foo

go 1.14

project/internal/foo/foo.go

package foo

import "fmt"

//Foo prints "Hello from Foo"
func Foo() {
    fmt.Println("Hello from Foo")
}

project/main.go

package main

import (
    "internal/bar"
    "internal/foo"
)

func main() {
    bar.Bar()
    foo.Foo()
}

Now the most important module project/go.mod

module project

go 1.14


require internal/bar v1.0.0
replace internal/bar => ./internal/bar
require internal/foo v1.0.0
replace internal/foo => ./internal/foo

Couple things here:

  1. You can have any name in require. You can have project/internal/bar if you wish. What Go think it is URL address for the package, so it will try to pull it from web and give you error
go: internal/[email protected]: malformed module path "internal/bar": missing dot in first path element

That is the reason why you need to have replace where you tell Go where to find it, and that is the key!

replace internal/bar => ./internal/bar
  1. The version doesn't matter in this case. You can have v0.0.0 and it will work.

Now, when you execute your code you will have

Hello from Bar
Hello from Foo

Here is GitHub link for this code example

Solution 2:

The packages have to be located in your $GOPATH in order to be imported. The example you gave with import "./internal/foo|bar" works because it does a local-import. internal only makes it so code that doesn't share a common root directory to your internal directory can't import the packages within internal.

If you put all this in your gopath then tried to import from a different location like OuterFolder/project2/main.go where OuterFolder contains both project and project2 then import "../../project/internal/foo" would fail. It would also fail as import "foo" or any other way your tried due to not satisfying this condition;

An import of a path containing the element “internal” is disallowed if the importing code is outside the tree rooted at the parent of the “internal” directory.

Now if you had the path $GOPATH/src/project then you could do import "foo" and import "bar" from within $GOPATH/src/project/main.go and the import would succeed. Things that are not contained underneath project however would not be able to import foo or bar.