Wednesday, August 5, 2015

Let's Make a Go (Golang) Package!

Go tends to be rigid in certain customs. That isn't to say you can't bend or break these customs, but the culture certain makes it clear that some things are...frowned upon.

For example, you can write fairly complicated programs using just a single .go file. Good coding practice is to put your reusable functions into a library (or in Go, a package).

I'm assuming a file structure similar to this for your workspace:

Go
     |
     |-bin
     |-pkg
     |-src

Now, let's create a small main() Go file.

Go
     |
     |-bin
     |-pkg
     |-src
          |-uses_a_package
               |-uses_a_package.go

I have a personal habit when creating files to test out theories or implementations of using the word "test" in the names. Go language developers frown on this practice because "test" is used as a literal code testing reserved word; one of the things I personally find screwy about the language, but it is what it is. If you put something like "uses_a_package_test.go" as a filename, you're in for some head scratching and Google searches leading you to articles about running application tests.

So in the file /go/src/uses_a_package/uses_a_package.go I put the following:


package main

import (
	"fmt"
	"package_demo"
)

func main() {

	// main() says hello
	fmt.Println("Hello from main()!")

	// Package, say hello
	package_demo.Howdy()

}

Now I need the package. I create a new directory and .go file:

Go
     |
     |-bin
     |-pkg
     |-src
          |-uses_a_package
               |-uses_a_package.go
          |-package_demo
               |-package_demo.go

In package_demo.go I enter the following:



// Package package_demo is demonstrating how to make a simple package file/library
package package_demo

import (
	"fmt"
)

// Howdy() says hello with no arguments and no value returned
func Howdy() {

	// Prints a hello to the console
	fmt.Println("Hello from the package_demo package!")

	return
}

Next I compile the everything. From /go/src/uses_a_package, I run:

go install

I didn't have to separately compile the package. Why?

The workspace now looks like this:


Go
     |
     |-bin
          |-uses_a_package
     |-pkg
          |-darwin_amd64
               |package_demo.a
     |-src
          |-uses_a_package
               |-uses_a_package.go
          |-package_demo
               |-package_demo.go

The answer is when I ran the "install" command, Go saw the extra package import and found that it, too, needed compilation. I can run the install command just on the package and it'll compile and insert it into my /go/pkg directory, but I didn't need to.

Running the application:

../../bin/uses_a_package

...yields the following output:

Hello from main()!

Hello from the package_demo package!

The way I added comments to the package is kind of important. It's another bit of knowledge that isn't necessarily intuitive, but you run across it in trying to get other things working in Go; the self-documenting feature of GoDoc.

From /go/src, I run:

godoc ./package_demo

Output is:

PACKAGE DOCUMENTATION

package package_demo
    import "./package_demo"

    Package package_demo is demonstrating how to make a simple package
    file/library

FUNCTIONS

func Howdy()
    Howdy() says hello with no arguments and no value returned

There is definitely room for improvement in how I documented things with comments, but you get the point. Properly comment your code in Go!

I should also note that GoDoc is used for the documentation on the main Go site. Visit that to get an idea how to properly document your functions and code...as I'm throwing together quick demo stuff for this, you can see I say something stupidly obvious like "no arguments and no return value."

Now, why is GoDoc run on the directory, and not on the file? For one, it doesn't work.

godoc ./package_demo/package_demo.go
2015/07/31 13:44:27 newDirectory(/target): not a package directory

Two, the directory acts like the package, and the files in it are the goodies. Let's try something...

in /go/src/package_demo, I create a new file called package_demo2.go and populate it with the following text:



// Package package_demo2 takes the package demonstration one step farther, showing one package
// directory is "merged" into one package despite multiple sub-files
package package_demo

import (
	"fmt"
)

// CustomHello takes a name passed as a parameter and prints a greeting to that string
func CustomHello(strName string) {

	// Print a custom hello!
	fmt.Println("Hello, " + strName + "!")

	return
}

...and add a call in main() to the added function:



package main

import (
	"fmt"
	"package_demo"
)

func main() {

	// main() says hello
	fmt.Println("Hello from main()!")

	// Package, say hello
	package_demo.Howdy()

	// Second package file, say hello to Bob!
	package_demo.CustomHello("Bob")
}

Compile, and run:

../../bin/uses_a_package
Hello from main()!
Hello from the package_demo package!
Hello, Bob!

At this point the directory structure looks like this:

Go
     |
     |-bin
          |-uses_a_package
     |-pkg
          |-darwin_amd64
               |package_demo.a
     |-src
          |-uses_a_package
               |-uses_a_package.go
          |-package_demo
               |-package_demo.go
               |-package_demo2.go


And of course a function in the package can return results. A quick edit to package_demo.go:


// Package package_demo is demonstrating how to make a simple package file/library
package package_demo

import (
	"fmt"
)

// Howdy() says hello with no arguments and no value returned
func Howdy() {

	// Prints a hello to the console
	fmt.Println("Hello from the package_demo package!")

	return
}

// HelloBack will return a string with a greeting to the caller rather than printing directly to the console
func HelloBack(strName string) string {

	// Who to say hello to?
	strName = "Hello there, " + strName + "!"

	return(strName)

}

...followed by a call added to uses_a_package.go:



package main

import (
	"fmt"
	"package_demo"
)

func main() {

	// main() says hello
	fmt.Println("Hello from main()!")

	// Package, say hello
	package_demo.Howdy()

	// Second package file, say hello to Bob!
	package_demo.CustomHello("Bob")

	// Back to the first file...
	fmt.Println(package_demo.HelloBack("Phil"))

}

Then compile and  test!

go install
../../bin/uses_a_package

Hello from main()!
Hello from the package_demo package!
Hello, Bob!
Hello there, Phil!

And of course GoDoc is helpful with documentation. From go/src, I ran "godoc ./package_demo":

PACKAGE DOCUMENTATION

package package_demo
    import "./package_demo"

    Package package_demo is demonstrating how to make a simple package
    file/library

    Package package_demo2 takes the package demonstration one step farther,
    showing one package directory is "merged" into one package despite
    multiple sub-files

FUNCTIONS

func CustomHello(strName string)
    CustomHello takes a name passed as a parameter and prints a greeting to
    that string

func HelloBack(strName string) string
    HelloBack will return a string with a greeting to the caller rather than
    printing directly to the console

func Howdy()

    Howdy() says hello with no arguments and no value returned

One thing to note is functions to be exported in the package...visible to the executable main()...must be capitalized.  An edit to our growing package_demo.go file:



// Package package_demo is demonstrating how to make a simple package file/library
package package_demo

import (
	"fmt"
)

// Howdy() says hello with no arguments and no value returned
func Howdy() {

	// Prints a hello to the console
	fmt.Println("Hello from the package_demo package!")

	return
}

// HelloBack will return a string with a greeting to the caller rather than printing directly to the console
func HelloBack(strName string) string {

	// Who to say hello to?
	strName = "Hello there, " + strName + "!"

	return(strName)

}

// CallsFuncInPackageSelf takes a name passed as an argument and calls another function within the package
// source, a non-exported (capitalized) function, to construct a reply
func CallsFuncInPackageSelf(strName string) {

	strResponse := constructString(strName)
	fmt.Println(strResponse)

}

// constructString crafts a greeting and returns a reply, but only works within the package
func constructString(strGreetMe string) string {

	return("A secret hello to " + strGreetMe + "!")

}

You'll notice I now added a CallsFuncInPackageSelf function, which when invoked calls "constructString" (lowercase first letter) to return a secret hello to the name that was passed to CallsFuncInPackageSelf. In main() I make a small addition:



package main

import (
	"fmt"
	"package_demo"
)

func main() {

	// main() says hello
	fmt.Println("Hello from main()!")

	// Package, say hello
	package_demo.Howdy()

	// Second package file, say hello to Bob!
	package_demo.CustomHello("Bob")

	// Back to the first file...
	fmt.Println(package_demo.HelloBack("Phil"))

	// Now a secret greeting...
	package_demo.CallsFuncInPackageSelf("Tom")

}
Run "go install" then call the resulting binary and:

../../bin/uses_a_package
Hello from main()!
Hello from the package_demo package!
Hello, Bob!
Hello there, Phil!
A secret hello to Tom!

But if I edit uses_a_package.go to directly call constructString("Tom") and try a recompile, I get this:

go install
# uses_a_package
./uses_a_package.go:26: cannot refer to unexported name package_demo.constructString

./uses_a_package.go:26: undefined: package_demo.constructString

In order for the function to be visible outside the package, it must begin with a capital letter. That's why when you're working with methods like fmt.Println() the function has a capital...in this case, it's exported from the fmt package!

I hope this is helpful in getting started creating packages in Go!

No comments:

Post a Comment