14

I am experiencing an issue where go run main.go produces the error:

# command-line-arguments
./main.go:9: undefined: test

However the commands go build && ./goruntest compile and run the program just fine.

The output is:

Hi from test()

Hi from sameFileTest()

Hi from pkgtest.Test()

Hi from pkgtest.Test1()

I have the directory set-up like so:

go/src/github.com/username/goruntest/
    pkgtest/
        pkgtest.go
        pkgtest1.go
    main.go
    test2.go

Here is the code.

main.go

package main

import (
    "fmt"
    "github.com/username/goruntest/pkgtest"
)

func main() {
    fmt.Println(test())           // main.go:9
    fmt.Println(sameFileTest())
    fmt.Println(pkgtest.Test())
    fmt.Println(pkgtest.Test1())
}

func sameFileTest() string {
    return "Hi from sameFileTest()"
}

gotest1.go

package main

func test() string {
    return "Hi from test()"
}

pkgtest/pkgtest.go

package pkgtest

func Test() string {
    return "Hi from pkgtest.Test()"
}

pkgtest/pkgtest1.go

package pkgtest

func Test1() string {
    return "Hi from pkgtest.Test1()"
}

I understand that the problem is the second file as part of package main and I also understand that there is no real reason to have a second file in main.

My question is: Why is go run unable to handle this set-up but building and running the executable works just fine?

EDIT

Included a second file in pkgtest

I also understand that the command go run main.go gotest1.go works but why do I need to specify gotest1.go?

I originally omitted these details for the sake of brevity. But now I see they are important to the question.

Community
  • 1
  • 1
robbmj
  • 16,085
  • 8
  • 38
  • 63
  • 1
    It's documented to compile and run "the named source files" passed on the command line. If I recall, it may not even rebuild imported packages that have had changes. You really want `go build` unless you're doing a really simple test script. – twotwotwo Nov 14 '14 at 00:42
  • @twotwotwo, I agree (+1), but in this case the named source file has a dependency that `go run` is not finding in `package main`. Why is that? – robbmj Nov 14 '14 at 01:03
  • I think they picked that behavior because it lets you have a collection of script-like .go files in one directory and `go run` any one of them without interference from the others. – twotwotwo Nov 14 '14 at 01:35
  • I've always wondered this, thanks for asking – Kyle Chadha Oct 24 '15 at 19:57

3 Answers3

11

Try providing all relevant files to go run

$ go help run
usage: go run [build flags] [-exec xprog] gofiles... [arguments...]

Run compiles and runs the main package comprising the named Go source files.
A Go source file is defined to be a file ending in a literal ".go" suffix.
dskinner
  • 10,527
  • 3
  • 34
  • 43
  • 1
    Fair enough +1, but why can `go run` handle multiple files in `pkgtest` but not multiples in main? – robbmj Nov 14 '14 at 00:50
  • 1
    well, quite simply, b/c it's imported in main.go, otherwise `go run` would be overly restrictive in having to supply every single go source file required to build. In this case, `go run` lets you selectively run a particular set of files you have defined. For the general case of simply running a `main` across multiple files, this does feel restrictive but it also allows some elbow room if you have a lot going on in that particular directory and only want to run a particular set of files. Otherwise a simple `go build && ./myapp` is another way to go. – dskinner Nov 14 '14 at 01:36
1

I want to share some useful resources here for reference, which I learned from another similar deleted post. It looks like a simple command but yet you may not fully know it.

  1. If you're supplying a list of source files to go run, you are creating a single synthesized package, and build constraints are ignored. So instead of listing go files, it's better to use an import path or a file system path, e.g. go run .. Check it with go help packages:
As a special case, if the package list is a list of .go files from a
single directory, the command is applied to a single synthesized
package made up of exactly those files, ignoring any build constraints
in those files and ignoring any other files in the directory.
  1. The executable target is named after the first source file. Check it in implementations for go run command here(on line 80 and 125) and here(on line 2506):
// GoFilesPackage creates a package for building a collection of Go files
// (typically named on the command line). The target is named p.a for
// package p or named after the first Go file for package main.
func GoFilesPackage(ctx context.Context, gofiles []string) *Package {
...
  1. To avoid potential problems during package initialization, you're encouraged to supply the list of go files in lexical file name order. Check it in the specs:
The declaration order of variables declared in multiple files is determined by the order in which the files are presented to the compiler: Variables declared in the first file are declared before any of the variables declared in the second file, and so on.
...
A package with no imports is initialized by assigning initial values to all its package-level variables followed by calling all init functions in the order they appear in the source, possibly in multiple files, as presented to the compiler.
...
To ensure reproducible initialization behavior, build systems are encouraged to present multiple files belonging to the same package in lexical file name order to a compiler. 
  1. There are historical discussions about go run, check it here and here.
sify
  • 645
  • 9
  • 19
0

The easiest way to run all *.go-files from the current package (directory) is

go run .

As mentioned above, this approach allows you to use script-like *.go-files in one directory and run any of them without interference from the others.

IStranger
  • 1,868
  • 15
  • 23