Go programming for .NET Devs — Working with multiple source files

As we start moving away from trivial programs, we need a way to split our code into multiple files. Of course, Go does support this feature, but not in the same way as .NET does.

In this post, we are going to take a look at how to create a Go “project” that allows us to distribute functions into multiple files.

Let’s start by doing a quick review of how we usually do it in .NET. Assuming you use Visual Studio, the typical path to create a console app is from the file menu:

File -> New Project -> Console Application

What Visual Studio does for us is to create a solution file, a project file, and a program file that contains the entry function for our application.

The solution and the project files are “metadata files.” Files that contains all the information that the compiler needs to build our program, input files, output files, target platform, encoding, and so on. Everything. So when we hit F5, the IDE knows how to build an run our program.

(Visual Studio also creates config files, assembly info, resources, etc. I skipped those because they are not relevant to the subject of this post).

When we add another file to our project, Visual Studio updates the project file, so the compiler knows how to find it at build time. The process is transparent to us. We don’t have to do anything different to run a program that contains a single file, or a thousand, for that matter, is about the same. We just hit F5 an everything works.

In Go, things are a bit different. For starters, there are no project files (At least no in basic setups). Most kinds of stuff are carried on by conventions, which is nice once you get used to them, but is a bit tricky is you are used to IDEs where everything just works.

As we said in the previous post, go files live inside packages, so let’s create a package for our console app.

I didn’t mention that before, but Go is really opinionated when it comes to source files location and project structure. On future posts, We’ll discuss how to configure and tweak a Go programming environment, but this time I’ll assume that you have a basic setup ready to roll.

Let’s start by creating the package folder and the program file.

$ cd $GOPATH/src
$ mkdir console
$ cd console
$ touch program.go

Now we add some code to our program file:

package main
import (
"fmt"
)
func main() {
fmt.Printf("Hello World!\n")
}

Finally, we run our program using the go run command to see if everything is working as expected:

$ go run program.go
Hello World!

Awesome!

Now let’s supposed we have to add a function to concatenate two strings. (Agreed, it has no sense in real life, but let’s go with it.)

package main
import (
"fmt"
)
func main() {
fmt.Printf(strcat("Hello ", "World!\n"))
}
func strcat(s1, s2 string) string {
return fmt.Sprintf("%s%s", s1, s2)
}

Let’s run our program one more time to see if “strcat” function works.

$ go run program.go
Hello World!

So far, so good. But we are still working on a single file. It’s time to create a new file and more the function over there.

Let’s call the file, utils.go.

$ touch utils.go
package main
import (
"fmt"
)
func strcat(s1, s2 string) string {
return fmt.Sprintf("%s%s", s1, s2)
}

Notice that we have to specify that the file belongs to the package main. That’s mandatory. It’s not enough to place the file inside the package folder. We must tell the compiler that the file belongs to the package.

All right, now it’s time to run our program and see if the Go compiler can figure out the location of the “strcat” function.

$ go run program.go
# command-line-arguments
./program.go:8:13: undefined: strcat

Nope. It’ s smart, but not that much ;)

The problem here was that we never tell the compiler to include all files from the package. As opposed to what happens in Visual Studio, there is no metadata file that the compiler can peek and see which files should be part of the build process. It does know the package name, it does know the program’s entry point, but it can’t figure out by itself which files we want to include.

Let’s give it another try, but this time using a wildcard to include all Go files from the current directory.

$ go run *.go
Hello World!

Sure enough, now it prints “Hello World!”.

Notice that we didn’t have to include/reference utils.go. As long as the file belongs to the same package where its functions are being used, the compiler is smart enough to work its way and include them for us.

That is for this post, next time we are going to take a look at control flow statements. Stay tuned!

PS: Don’t forget to clap if you like this post!