blob: e8a26b21c90821d48e43db3cd021df6098486a1c [file] [log] [blame]
Get started with Go
Andrew Gerrand
Google
https://plus.google.com/106356964679457436995
@enneff
https://go.dev
* Background
# In this tutorial I'll show you how to install and use the Go Programming Language.
* What is Go?
Go is a new, general-purpose programming language.
- Compiled
- Statically typed
- Concurrent
- Simple
- Productive
"Go is a wise, clean, insightful, fresh thinking approach to the greatest-hits subset of the well understood."
- Michael T. Jones
* History
- Project starts at Google in 2007 (by Griesemer, Pike, Thompson)
- Open source release in November 2009
- More than 250 contributors join the project
- Version 1.0 release in March 2012
# Go was originally built by a team at Google, led by Robert Griesemer, Rob Pike, and Ken Thompson. In November 2010, Go was launched publicly as an open source project. Since then, a team at Google and more than 250 contributors from the open source community continued to improve the Go language, libraries, and tools.
# In March 2012, we announced Go 1, a version of the language and libraries that will be supported for years to come.
* Getting started
* Install Go
.link /doc/install go.dev/doc/install
- Install from binary distributions or build from source
- 32- and 64-bit x86 and ARM processors
- Windows, Mac OS X, Linux, and FreeBSD
- Other platforms may be supported by `gccgo`
# Go is available in binary form for Windows, Mac OS X, Linux, and FreeBSD running under 32 and 64-bit x86 processor architectures. To install Go on these systems, download the appropriate file from golang.org and either open the package installer (Windows or OS X) or extract the archive to /usr/local/go (Linux and FreeBSD).
# If you use a different operating system or processor architecture, you may still be able to use Go by building it from source or using gccgo (an alternate Go compiler based on the GNU C Compiler). See the installation instructions on golang.org for how to do this.
* Test your Go installation
# Let's build and run a simple Go program to check that we have a valid Go install. Create a file named hello.go somewhere convenient and populate it with this Go source code:
Put this code into `hello.go`:
.code tutorial/hello.go
# Now, from a command prompt, run `go run hello.go`. This will build your program, storing the executable binary in a temporary location, and run it. You should see the greeting printed to the console.
Run the program:
$ go run hello.go
* The go tool
The `go` tool is the standard tool for building, testing, and installing Go programs.
Compile and run `hello.go`:
$ go run hello.go
Run `zip` tests:
$ go test archive/zip
Build and format the files in the current directory:
$ go build
$ go fmt
Fetch and install `websocket`:
$ go get code.google.com/p/go.net/websocket
* Workspaces
The `go` tool derives build instructions from Go source code.
There's no need to write and maintain build scripts.
For this to work, some prescribed directory structure, known as a workspace, is required.
workspace/
bin # executable binaries
pkg # compiled object files
src # source code
* Create a workspace
Create your workspace now.
#I prefer to use the name "gocode", but you can use whatever you like.
$ mkdir -p $HOME/gocode/src
(The `bin` and `pkg` sub-directories will be created by the `go` tool.)
Tell the `go` tool where your workspace is by setting the `GOPATH` environment variable:
# You can do this on OS X, Linux, and FreeBSD by adding this line to the `$HOME/.profile` and re-starting any running shells:
export GOPATH=$HOME/gocode
# See the installation instructions at golang.org for how to set `GOPATH` under Windows.
You may also want to add the `bin` sub-directory of your workspace to your `PATH`:
export PATH=$PATH:$GOPATH/bin
This lets you run your Go programs without specifying their full path.
(You may want to put these `export` commands in the `.bash_profile` file in your home directory.)
* Choose a namespace
Choose a special place for your Go code.
I use `"github.com/nf"`, the root of my GitHub account (useful with `go get`).
# Once you have chosen a namespace, create the required paths inside the `src` directory of your workspace:
$ mkdir -p $GOPATH/src/github.com/nf
Create a `hello` directory in your namespace and copy `hello.go` there:
$ mkdir $GOPATH/src/github.com/nf/hello
$ cp hello.go $GOPATH/src/github.com/nf/hello
Now you can build install the hello program with the `go` tool:
$ go install github.com/nf/hello
This builds an executable named `hello`, and installs it to the `bin` directory of your workspace.
$ $GOPATH/bin/hello
Hello, fellow gopher
* Our project
* Our project
A command-line program that fetches and displays the latest headlines from the `golang` page on Reddit.
# (Reddit is a huge link aggregation site where people submit links and other people vote on and have discussions about them.)
The program will:
- make an HTTP request to the Reddit API,
- decode the JSON response into a Go data structure, and
- print each link's title, URL, and number of comments.
To get started, create directory inside your namespace called `reddit`:
$ mkdir $GOPATH/src/github.com/nf/reddit
This is where you will put your Go source files.
* Make an HTTP request
This program makes an HTTP request to the Reddit API and copies its response to standard output. Put this in a file named `main.go` inside your `reddit` directory.
.code tutorial/1get.go
# If you run it you should see a blob of JSON data, or an error message if something goes wrong.
# There's a lot going on here, so let's break it down.
* Make an HTTP request: package statement
All Go code belongs to a package.
.code tutorial/1get.go /package/
Go programs begin with function `main` inside package `main`.
* Make an HTTP request: import statement
The import declaration specifies the file's dependencies.
.code tutorial/1get.go /import/,/\)/
Each string is an import path. It tells the Go tools where to find the package.
These packages are all from the Go standard library.
* Make an HTTP request: function declaration
.code tutorial/1get.go /func.main/,/^}/ HLfunc
This is a function declaration. The main function takes no arguments and has no return values.
* Make an HTTP request: http.Get
.code tutorial/1get.go /func.main/,/^}/ HLget
Call the `Get` function from the `http` package, passing the URL of the Reddit API as its only argument.
Declare two variables (`resp` and `err`) and give them the return values of the function call. (Yes, Go functions can return multiple values.) The `Get` function returns `*http.Response` and an `error` values.
* Make an HTTP request: error handling
.code tutorial/1get.go /func.main/,/^}/ HLerr
Compare `err` against `nil`, the zero-value for the built-in `error` type.
The `err` variable will be nil if the request was successful.
If not, call the `log.Fatal` function to print the error message and exit the program.
# I cannot overstate the importance of error checking. Disregard error values at your peril!
* Make an HTTP request: check status
.code tutorial/1get.go /func.main/,/^}/ HLstatus
Test that the HTTP server returned a "200 OK" response.
If not, bail, printing the HTTP status message ("500 Internal Server Error", for example).
* Make an HTTP request: copy
.code tutorial/1get.go /func.main/,/^}/ HLcopy
Use `io.Copy` to copy the HTTP response body to standard output (`os.Stdout`).
#On the left is a variable assignment that stores the error return value of the Copy in err. Note that this is different to the previous assignment which was also a declaration. This simple assignment - distinguished by the absence of a colon - assigns the second return value of the Copy to the err variable. The first return value, which appears to be assigned to an underscore, is thrown away. The underscore can be thought of as a "write only variable".
package io
func Copy(dst Writer, src Reader) (written int64, err error)
The `resp.Body` type implements `io.Reader` and `os.Stdout` implements `io.Writer`.
* Decoding the JSON response
* Data structures
The Reddit API returns JSON data like this:
{"data": {"children": [
{"data": {
"title": "The Go homepage",
"url": "https://go.dev/",
...
}},
...
]}}
Go's `json` package decodes JSON-encoded data into native Go data structures. To decode the API response, declare some types that reflect the structure of the JSON data:
.code tutorial/2json.go /type.Item/,$
* Decode the response
Instead of copying the HTTP response body to standard output
.code tutorial/1get.go /io.Copy/
we use the json package to decode the response into our Response data structure.
.code tutorial/2json.go /new.Response/,/json.NewDecoder/
Initialize a new `Response` value, store a pointer to it in the new variable `r`.
Create a new `json.Decoder` object and decode the response body into `r`.
As the decoder parses the JSON data it looks for corresponding fields of the same names in the `Response` struct. The `"data"` field of the top-level JSON object is decoded into the `Response` struct's `Data` field, and JSON array `"children"` is decoded into the `Children` slice, and so on.
* Print the data
for _, child := range r.Data.Children {
fmt.Println(child.Data.Title)
}
Iterate over the `Children` slice, assigning the slice value to `child` on each iteration.
The `Println` call prints the item's `Title` followed by a newline.
* Tidying up
* Separation of concerns
So far, all the action happens in the main function.
As the program grows, structure and modularity become important.
What if we want to check several subreddits? Or share this functionality with another program?
Create a function named `Get` that takes the name of subreddit, makes the API call, and returns the items from that subreddit.
.code tutorial/3func.go /func.Get/
`Get` takes a string, `reddit`, and returns a slice of `Item` and an `error` value.
* Get: construct the URL
.code tutorial/3func.go /func.Get/,/^}/ HLurl
Use `fmt.Sprintf` to construct the request URL from the provided `reddit` string.
* Get: return
.code tutorial/3func.go /func.Get/,/^}/ HLreturn
Exiting the function, return a nil slice and a non-nil error value, or vice versa.
* Get: making an error
.code tutorial/3func.go /func.Get/,/^}/ HLerrors
The response's `Status` field is just a string; use the `errors.New` function to convert it to an `error` value.
* Get: defer clean-up work
.code tutorial/3func.go /func.Get/,/^}/ HLclose
Defer a call to the response body's `Close` method, to guarantee that we clean up after the HTTP request. The call will be executed after the function returns.
# It's important to Close the response Body, because it tells the http package that you have finished reading, freeing up the HTTP connection to for re-used (the Go http package uses Keep-Alive and connection pooling by default). If we want to use our Get function in the context of a larger program, it must clean up properly.
# TODO(adg): about defer
* Get: prepare the response
.code tutorial/3func.go /func.Get/,/^}/ HLprepare
Use the make function to allocate an `Item` slice big enough to store the response data.
* Get: convert the response
.code tutorial/3func.go /func.Get/,/^}/ HLconvert
Iterate over the response's `Children` slice, assigning each child's `Data` element to the corresponding element in the items slice.
* Use Get in main
In the `main` function, replace the http request and JSON decoding code with a single call to `Get`.
.code tutorial/3func.go /func.main/,/^}/
The print loop becomes clearer, too.
However, it's not very useful to print only the title of the items. Let's address that.
* Formatted output
* The Stringer interface
The `fmt` package knows how to format the built-in types, but it can be told how to format user-defined types, too.
When you pass a value to the `fmt.Print` functions, it checks to see if it implements the `fmt.Stringer` interface:
type Stringer interface {
String() string
}
Any type that implements a `String() string` method is a `Stringer`, and the `fmt` package will use that method to format values of that type.
* Formatting Items
A method declaration is just like a function declaration, but the receiver comes first.
Here's a `String` method for the `Item` type that returns the title, a newline, and the URL:
func (i Item) String() string {
return fmt.Sprintf("%s\n%s", i.Title, i.URL)
}
To print the item we just pass it to Println, which uses the provided `String` method to format the `Item`.
fmt.Println(item)
* Richer formatting (1/2)
Let's go a step further. One way to judge how interesting a link might be is by the discussion surrounding it. Let's display the number of comments for each `Item` as well.
{ "title": "The Go homepage",
"url": "https://go.dev/",
"num_comments": 10 }
Update the `Item` type to include a `Comments` field:
.code tutorial/4method.go /type.Item/,/^}/
The new `Comments` field has a "struct tag", a string that annotates the field. Go code can use the `reflect` package to inspect this information at runtime.
This tag, `json:"num_comments"`, tells the `json` package to decode the `"num_comments"` field of the JSON object into the `Comments` field (and the reverse, when encoding).
* Richer formatting (2/2)
Now the `String` method can be a little more complex:
.code tutorial/4method.go /func..i.Item..String/,/^}/
Observe that, unlike some languages, Go's switch statements do not fall through by default.
Now when we run our program we should see a nicely formatted list of links.
* Packages
* A new package (1/3)
This is useful code. Let's organize it to make it more accessible to others by putting it in an importable package.
Create a new directory inside your `reddit` directory named `geddit`, and copy your `main.go` file there.
`reddit` is the name of the library and `geddit` as that of the command-line client.
$ cd $GOPATH/src/github.com/nf/reddit
$ mkdir geddit
$ cp main.go geddit/
Rename the `main.go` inside the `reddit` directory to `reddit.go`. (Not necessary; just a convention.)
$ mv main.go reddit.go
* A new package (2/3)
Change the package statement at the top of `reddit.go` from `package main` to `package reddit`.
It is convention that the package name be the same as the last element of the import path.
The convention makes packages predictable to use:
import "github.com/nf/reddit"
func foo() {
r, err := reddit.Get("golang") // "reddit" here is the package name
// ...
}
The only strict requirement is that it must not be `package main`.
Also remove the `main` function from `reddit.go`, and any unused package imports. (The compiler will tell you which packages are unused.)
* A new package (3/3)
The `reddit.go` file now looks like this:
package reddit
import (
// omitted
)
type Response struct {
// omitted
}
type Item struct {
// omitted
}
func (i Item) String() string {
// omitted
}
func Get(reddit string) ([]Item, error) {
// omitted
}
* Using the reddit package
Edit the `geddit/main.go` file to remove the `Get`, `Item`, and `Response` declarations, import the `reddit` package, and use the `reddit.` prefix before the `Get` invocation:
.code tutorial/main.go
* Documentation
* Documentation (1/3)
`Godoc` is the Go documentation tool. It reads documentation directly from Go source files. It's easy to keep documentation and code in sync when they live together in the same place.
Here's our reddit package when viewed from `godoc`:
$ godoc github.com/nf/reddit
PACKAGE
package reddit
import "github.com/nf/reddit"
FUNCTIONS
func Get(reddit string) ([]Item, error)
TYPES
type Item struct {
Title string
URL string
Comments int `json:"num_comments"`
}
func (i Item) String() string
type Response struct {
// etc
# Here we see all our top-level declarations. This is our package's user interface (not to be confused with an interface type). Despite the lack of documentation, the one thing that jumps out at me is the Response type. This is an implementation detail, and not something that a user of this package should need to see.
# In Go, top-level declarations beginning with an uppercase letter are "exported", and therefore visible outside the package. All other names are private and inaccessible to code outside the package. We can hide the Response type by simply renaming it to "response".
* Documentation (2/3)
First, hide the `Response` type by renaming it to `response`.
In Go, top-level declarations beginning with an uppercase letter are "exported" (visible outside the package). All other names are private and inaccessible to code outside the package.
To document the remaining visible names, add a comment directly above their declarations:
.code tutorial/reddit/reddit.go /Item.describes/,/type/
.code tutorial/reddit/reddit.go /Get.fetches/,/func/
# It is the convention to begin godoc comments with the noun they describe. This allows the text to be displayed in a variety of contexts, not necessarily alongside the declaration itself.
Most importantly, document the package itself by adding a comment to the package clause:
.code tutorial/reddit/reddit.go /Package.reddit/,/package.reddit/
Don't worry about documenting the `String` method, as all Go programmers should be familiar with it and its purpose.
* Documentation (3/3)
The `godoc` output for our revised package:
PACKAGE
package reddit
import "github.com/nf/reddit"
Package reddit implements a basic client for the Reddit API.
FUNCTIONS
func Get(reddit string) ([]Item, error)
Get fetches the most recent Items posted to the specified subreddit.
TYPES
type Item struct {
Title string
URL string
Comments int `json:"num_comments"`
}
Item describes a Reddit item.
func (i Item) String() string
# You can also see how it will look on the web by running a local godoc instance
# $ godoc -http=localhost:8080
# and loading http://localhost:8080/ in your browser.
* Sharing your work
* Publish the reddit library
- Add a copyright notice to each file
- Choose your preferred license
- Initialize the source control repo
- Make your first commit
- Push it to the remote repository
* Install with go get
Use the go tool to automatically check out and install Go code:
$ go get github.com/nf/reddit/geddit
This checks out the repository to `$GOPATH/src/github.com/nf/reddit` and installs the binary to `$GOPATH/bin/geddit`. The `bin` directory is in my PATH, so I can now run:
$ geddit
The `go get` command can fetch code from
- Bitbucket
- GitHub
- Google Code
- Launchpad
as well as arbitrary Git, Mercurial, Subversion, and Bazaar repositories.
* Homework
Some ideas:
- Implement a command-line interface to specify the subreddit(s) to query.
- Expand the reddit package to support more of the Reddit API.
- Learn about Go's concurrency primitives and perform multiple requests in parallel.
* Where to go from here
Learn Go:
.link /tour/ go.dev/tour
Documentation and articles:
.link /doc go.dev/doc
Standard library reference:
.link /pkg go.dev/pkg