03 Library Source Files

03 Library Source Files #

Have you ever written a small command (or micro program) in Go language?

When you were writing “Hello, world”, one source code file was enough. Although this kind of little thing is not useful, it can give you a little sense of achievement. If you are not satisfied with this little bit, don’t worry, just keep learning, I’m sure you can also write powerful programs.


In the previous article, we learned about command source code files. In addition to command source code files, you can also use Go language to write library source code files. So what is a library source code file?

In my definition, a library source code file is a source code file that cannot be executed directly. It is used only to store program entities, which can be used by other code (as long as they comply with the Go language specification).

The “other code” can be in the same source code file as the program entity being used, in other source code files, or even in other packages.

So what are program entities? In Go language, program entities refer to variables, constants, functions, structures, and interfaces.

We always declare (or define) program entities first and then use them. For example, in the example from the previous article, we first defined the variable name and then used it when calling the fmt.Printf function in the main function.

One more thing, the name of a program entity is called an identifier. An identifier can be any letter, digit, or underscore “_” represented by Unicode encoding, but its first character cannot be a digit.

In theory, we can use Chinese characters as variable names. However, I think this naming convention is very bad, and I would also explicitly prohibit it in my development team. As a qualified programmer, we should strive to write programs that meet international standards.

Back to the topic.

Today’s question is: How to split the code from a command source code file into other library source code files?

Let’s demonstrate this problem with some code to make it more specific.

If there is a command source code file named demo4.go in a certain directory, as follows:

package main

import (
	"flag"
)

var name string

func init() {
	flag.StringVar(&name, "name", "everyone", "The greeting object.")
}

func main() {
	flag.Parse()
	hello(name)
}

You should be quite familiar with the code above. I posted a very similar code snippet when I was talking about command source code files, and that source code file was named demo2.go.

The difference between these two files is that demo2.go directly prints the greeting by calling the fmt.Printf function, while the current demo4.go calls a function named hello at the same position.

The function hello is declared in another source code file, which I named demo4_lib.go, and I put it in the same directory as demo4.go. Here’s the code:

// Add code here.[1]

import "fmt"

func hello(name string) {
	fmt.Printf("Hello, %s!\n", name)
}

So here comes the question: What code should be added at comment [1]?

Typical Answer #

The answer is very simple: add the code package declaration statement package main. Why? As I mentioned before, all source code files in the same directory need to be declared as belonging to the same package.

If there is a command source code file in the directory, in order to compile all the files in the same directory, other source code files should also declare that they belong to the main package.

By doing so, we can run them. For example, we can run the following command in the directory where these files are located and get the corresponding result.

$ go run demo4.go demo4_lib.go 
Hello, everyone!

Alternatively, you can build the current code package first and then run it.

$ go build puzzlers/article3/q1
$ ./q1            
Hello, everyone!

Here, I have put both demo4.go and demo4_lib.go in a directory with a relative path of puzzlers/article3/q1.

By default, the import path of the corresponding code package will be the same. We can use the import path of the code package to refer to the program entities declared in it. However, the situation here is different.

Note that both demo4.go and demo4_lib.go declare themselves as belonging to the main package. I mentioned this usage earlier when discussing the organization of Go language source code, which is: the package name declared in the source code file can be different from the name of the directory it is in, as long as the package names declared in these files are consistent.

By the way, I have created a project named “Golang_Puzzlers” for this column. The src subdirectory of this project will contain all the code and related files we are discussing.

In other words, the correct usage is that you need to download the packaging file of the project to any directory on your local machine, and then add the “Golang_Puzzlers” directory to the environment variable GOPATH after decompression. Remember? This will make the “Golang_Puzzlers” directory one of the working spaces.

Problem Analysis #

This problem tests the basic rules of code package declaration. Let’s summarize them here.

The first rule is that the code package declaration statements of source code files in the same directory should be consistent. In other words, they should belong to the same code package. This applies to all source code files.

If there are command source code files in the directory, other types of source code files should also declare that they belong to the main package. This is a prerequisite for successfully building and running them.

The second rule is that the name of the code package declared in the source code file can be different from the name of the directory where it is located. When building the code package, the main name of the resulting file will be the same as the name of its parent directory.

For command source code files, the main name of the built executable file will be the same as the name of its parent directory, which I have also confirmed in my previous answer.

Well, after my repeated emphasis, I believe you have remembered these rules. The following content will also be related to them.

When writing real programs, it is not enough to just split the code into several source code files. We often use modular programming to place them in different code packages based on the functionality and purpose of the code. However, this will involve some rules for organizing code in the Go language. Let’s continue to look at them together.

In-depth Explanation #

1. How to split code in a command source file into other code packages? #

Let’s not focus on the techniques of splitting code for now. Here, I still use the method of splitting mentioned earlier. I save demo4.go as demo5.go and put it in a directory relative to the path puzzlers/article3/q2.

Then I create another directory relative to the path puzzlers/article3/q2/lib and make a copy of demo4_lib.go and rename it as demo5_lib.go and put it in that directory.

Now, to make them compile, how should we modify the code? You can think about it first. Here, I will give you part of the answer and we can take a look at the modified demo5_lib.go file together.

package lib5

import "fmt"

func Hello(name string) {
    fmt.Printf("Hello, %s!\n", name)
}

As you can see, I made two changes here. The first change is that I changed the package declaration statement from package main to package lib5. Note that I deliberately made the declared package name different from the name of the directory it is in. The second change is that I changed the lowercase function name hello to uppercase Hello.

Based on these changes, let’s look at the following questions.

2. Does the import path of a code package always match its relative path to the directory it is in? #

The relative path to the library source file demo5_lib.go is puzzlers/article3/q2/lib, but it declares itself as belonging to the lib5 package. In this case, is the import path of this package puzzlers/article3/q2/lib or puzzlers/article3/q2/lib5?

This question often confuses beginners of the Go language, and even those who have developed programs in Go may not be clear. Let’s take a look together.

First, when building or installing this code package, the path provided to the go command should be the relative path of the directory, like this:

go install puzzlers/article3/q2/lib

This command will be successfully completed. After that, the pkg subdirectory of the current workspace will generate the corresponding archive file, with the specific relative path:

pkg/darwin_amd64/puzzlers/article3/q2/lib.a

The darwin_amd64 here is the platform-specific directory I mentioned when discussing the workspace. As you can see, the relative path here corresponds to the relative path of the source code file.

To further illustrate the problem, I need to make two changes to demo5.go. The first change is to add puzzlers/article3/q2/lib in the code package import statement starting with import, attempting to import this code package.

The second change is to change the call to the hello function to a call to the lib.Hello function. The lib. here is called a qualifier, which is used to indicate the code package where the right program entity is located. However, it is different from the complete notation of the code package import path, only including the last level of the path, lib, which is consistent with the code package declaration statement rule.

Now, we can try running the go run demo5.go command. The error message will be similar to this:

./demo5.go:5:2: imported and not used: "puzzlers/article3/q2/lib" as lib5
./demo5.go:16:2: undefined: lib

The meaning of the first error message is that we imported the puzzlers/article3/q2/lib package, but did not actually use any program entity in it. This is not allowed in the Go language and will cause a compile-time failure.

Note that there is another clue here, “as lib5”. This indicates that although the puzzlers/article3/q2/lib package is imported, when using its program entities, it should be qualified with lib5.. This is the reason for the second error message. The Go command cannot find the code package corresponding to the qualifier lib..

Why is it so? The fundamental reason is that the declared code package in the source code file is different from the name of the directory it is in. Please remember that the relative path of the source code file to the src directory is its code package import path, and the qualifier given when actually using its program entities should correspond to the name of the code package it declares.

There are two ways to make the above build successfully. I choose to change the code package declaration statement in the demo5_lib.go file to package lib. The reason is that, in order to avoid confusing the users of this code package, we should always make the declared package name consistent with the name of its parent directory.

3. What kind of program entities can be referenced by code outside the current package? #

You may wonder why I capitalized the first letter of the function name hello in the demo5_lib.go file. In fact, this is related to the rules of access permissions for program entities in the Go language.

It’s super simple—program entities with a first letter capitalized in their names can be referenced by code outside the current package; otherwise, they can only be referenced by other code in the current package.

By naming convention, the Go language naturally divides the access permissions of program entities into package-private and public. For package-private program entities, even if you import the code package they belong to, you cannot reference them.

4. Are there any other rules for accessing program entities? #

The answer is yes. In Go 1.5 and later versions, we can create an internal code package to make some program entities only accessible by other code within the same module. This is called the third access permission of Go program entities: module-private.

The specific rule is that publicly declared program entities in the internal code package can only be referenced by code in the direct parent package and its sub-packages. Of course, you need to import this internal package before referencing it. Importing this internal package in other code packages is illegal and will not compile.

The puzzlers/article3/q4 package in the “Golang_Puzzlers” project has a simple example for you to examine. You can modify the code there and experience the role of the internal package.

Summary #

In this article, we have discussed in detail the methods of separating code from command source files, including splitting them into other library source files and splitting them into other code packages.

This involves several important basic coding rules in Go language, namely: code package declaration rules, code package import rules, and program entity access permission rules. When programming in a modular way, you must remember these rules, otherwise your code may not be able to compile.

Discussion Questions #

This set of discussion questions is about importing code packages, as follows.

  1. If you need to import two code packages, and the last level of the import paths for these two code packages is the same, for example: dep/lib/flag and flag, will there be a conflict?
  2. If there is a conflict, how can you resolve it? Are there multiple ways?

The first question is relatively simple, you will know it once you try. It is strongly recommended that you write an example, run the go command to build it, and see what prompts you receive.

As for the second question, it involves the advanced syntax of code package import statements. You may need to consult the Go language specification. However, it is not difficult either. How many solutions can you come up with at most? You can leave me a message, and we can discuss it together.

Click here to view the detailed code accompanying the Go Language column article.