The replace directive

At the risk of revealing how long I was not reading the docs about this, I recently learned how to use the replace directive.

I was following along with a Go project tutorial when I hit a wall I was familiar with: needing to import a local package as a dependency.

Before enabling modules, I’d gone through Jon Calhoun’s wonderful Gophercises. As practice, I’d at some point made all of the exercises into packages usable from the outside, and put the external and consumer packages in their own separate directories under $GOPATH/src/github.com/kristakoch/gophercises. This worked just fine at first, but later on after Go 1.11 was released and I started defaulting to modules enabled, I went in to try to use those same consumer packages and got errors from the compiler saying it couldn’t find what I was telling it to. After a couple hours of disorganized struggle, I threw up a white flag and crammed the external package files all the way back into the same packages where they were used.

But that was months ago, and this time I was a bit less intimidated. I took some time to read up on replace and analyze the go.mods I’d been using that’d been set up by other people, and this time things worked out.

What I ended up doing

  1. Ran go mod init in code/src/github.com/kristakoch/somepkg/cmd/somepkg to generate a go.mod in the directory where my runnable file was. (cmd is nested like that for structure and executable naming)

  2. Ran go mod init in code/src/github.com/kristakoch/somepkg to generate a go.mod in the directory of the package I was planning to use.

  3. In my main.go file, I added the import path for my package as:

    "github.com/kristakoch/somepkg"
    

    because that’s where I knew the package would be if I had pushed it up to GitHub.

  4. I was getting errors saying that the package couldn’t be found, which made sense because the code didn’t actually exist remotely, where Go was looking. In my go.mod, I then added:

    replace github.com/kristakoch/somepkg => ../../../somepkg
    

    which told Go to replace the import for the package with the package at a relative path three directories up from where the go.mod was. So three up from code/src/github.com/kristakoch/somepkg/cmd/somepkg/go.mod.

What I could have done

Of course there are other ways to do this. Following along with How to Write Go Code gives a different runthrough for how to import a local package.

In the tutorial, you:

  1. Create a package at $HOME/hello/hello.go
  2. Run go mod init [desired import path] to create a new module
  3. Create the package you’d want to use in the root directory of the go.mod
  4. Add your dependency in main.go as "[desired import path]/[ext package name]"

Here, you don’t have to create a go.mod in your external package. The external package is already contained in that module, which can contain multiple packages. Creating the new module also lets you develop the code outside of your GOPATH.

A module declares its identity in its go.mod via the module directive, which provides the module path. The import paths for all packages in a module share the module path as a common prefix - Go wiki

So I could have instead declared a module path where my go.mod was and changed my directory structure so that the package was nested inside of that folder, and imported it using the module path I defined in the go.mod, in that case not needing replace at all. Huh!