3. The Go Language

3.1 Why Go?

The Go language has:

  • Incredible runtime speed.
  • Amazing concurrency abstraction (goroutines.)
  • A great batteries-included standard library
  • Ease of deployment

3.2 Whirlwind Tour of Go

3.2.1 Installation

Download Go and follow the installation instructions.

On OSX, you can download the go1.9.3.darwin-amd64.pkg package file, open it, and follow the prompts to install the Go tools. The package installs the Go distribution to /usr/local/go.

To test your Go installation, open a new terminal and enter:

1 $ go version
2 go version go1.9.2 darwin/amd64

Then, add the following to your ~/.bashrc to set your GOROOT and GOPATH environment variables:

1 export GOROOT=/usr/local/go
2 export GOPATH=/Users/<your.username>/gopath
3 export PATH=$PATH:$GOROOT/bin:$GOPATH/bin%
1 source ~/.bashrc

Note that your GOPATH should be the directory under which your source and third-party packages will live under.

Next, try setting up a workspace: create a directory in $GOPATH/src/learn-go/ and in that directory create a file named hello.go.

1 $ mkdir learn-go
2 $ cd learn-go
3 $ touch hello.go
1 // hello.go
2 
3 package main
4 
5 import "fmt"
6 
7 func main() {
8    fmt.Printf("hello, world\n")
9 }

Run your code by calling go run hello.go. You can also go build Go programs into binaries, which lets us execute the built binary directly:

1 $ go build hello.go

The command above will build an executable named hello in the directory alongside your source code. Execute it to see the greeting:

1 $ ./hello
2 hello, world

If you see the “hello, world” message then your Go installation is working!

In the sub-sections that follow, we’ll quickly run through the basics of the Go language.

3.2.2 Types

Go is a statically-typed language; it comes with several built-in types such as strings, integers, floats, and booleans.

The types.go program below demonstrates Go’s basic built-in types:

 1 // types.go
 2 
 3 package main
 4 
 5 import "fmt"
 6 
 7 func main() {
 8 
 9   // Strings
10   fmt.Println("go" + "lang")
11 
12   // Integers
13   fmt.Println("1+1 =", 1+1)
14 
15   // Floats
16   fmt.Println("7.0/3.0 =", 7.0/3.0)
17 
18   // Booleans
19   fmt.Println(true && false)
20   fmt.Println(true || false)
21   fmt.Println(!true)
22 }

The Go programs shown in this chapter is available as part of the sample code included with this book.

Running the above program from the terminal gives you the following:

1 $ go run types.go
2 1+1 = 2
3 7.0/3.0 = 2.3333333333333335
4 false
5 true
6 false

3.2.3 Variables

Variables in Go always have a specific type and that type cannot change. You declare variables with var, or the := syntax to both declare and initialize a variable.

The variables.go program below demonstrates how to declare and initialize variables:

 1 // variables.go
 2 
 3 package main
 4 
 5 import "fmt"
 6 
 7 func main() {
 8 
 9   // Use var to declare 1 or more variables.
10   var a string = "initial"
11   fmt.Println(a)
12 
13   var b, c int = 1, 2
14   fmt.Println(b, c)
15 
16   // Go can infer a variable's type.
17   var d = true
18   fmt.Println(d)
19 
20   // Types have a default zero-value, for example `int` has a `0` zero-value.
21   var e int;
22   fmt.Println(e)
23 
24   // The := shorthand below is equivalent to `var f string = "short"`.
25   f := "short"
26   fmt.Println(f)
27 }

Running the above program from the terminal gives you:

1 $ go run variables.go
2 initial
3 1 2
4 true
5 0
6 short

3.2.4 Branching

3.2.4.1 If/else

Branching with if and else in Go is straightforward:

 1 // if.go
 2 
 3 package main
 4 
 5 import "fmt"
 6 
 7 func main() {
 8   // Parentheses around expression are optional
 9   if 7%2 == 0 {
10     fmt.Println("7 is even")
11   } else {
12     fmt.Println("7 is odd")
13   }
14 
15   // A statement can precede conditionals.
16   // Any variables declared in this statement are available in all branches.
17   if num := 9; num < 0 {
18     fmt.Println(num, "is negative")
19   } else if num < 10 {
20     fmt.Println(num, "has 1 digit")
21   } else {
22     fmt.Println(num, "has multiple digits")
23   }
24 }

Running the above program from the terminal gives you:

1 $ go run if.go
2 7 is odd
3 9 has 1 digit
3.2.4.2 Switches

Switch statements express conditionals across many branches. The switch.go program below demonstrates different ways you can use switches to perform branching logic:

 1 // switch.go
 2 
 3 package main
 4 
 5 import "fmt"
 6 import "time"
 7 
 8 func main() {
 9   // A basic switch
10   i := 2
11   fmt.Print("Write ", i, " as ")
12   switch i {
13   case 1:
14     fmt.Println("one")
15   case 2:
16     fmt.Println("two")
17   case 3:
18     fmt.Println("three")
19   case 4, 5, 6: // Branches with multiple clauses
20     fmt.Println("either four, five, or six")
21   default: // A wildcard branch
22     fmt.Println("something else")
23   }
24 
25   // Expressions can be non-constants
26   t := time.Now()
27   switch {
28   case t.Hour() < 12:
29       fmt.Println("It's before noon")
30   default:
31       fmt.Println("It's after noon")
32   }
33 
34   // A type switch lets you discover the type of a value
35   whatAmI := func(i interface{}) {
36     switch t := i.(type) {
37     case bool:
38       fmt.Println("I'm a bool")
39     case int:
40       fmt.Println("I'm an int")
41     default:
42       fmt.Printf("Don't know type %T\n", t)
43     }
44   }
45   whatAmI(true)
46   whatAmI(1)
47   whatAmI("hey")
48 }

Running the above program from the terminal gives you:

1 $ go run switch.go
2 Write 2 as two
3 It's after noon
4 I'm a bool
5 I'm an int
6 Don't know type string

3.2.5 Data Structures

3.2.5.1 Slices

Slices in Go are dynamically sized arrays. The slices.go program below shows how you can initialize, read, and modify slices:

 1 // slices.go
 2 
 3 package main
 4 
 5 import "fmt"
 6 
 7 func main() {
 8 
 9   // Declare and initialize a slice
10   s := make([]string, 3)
11   fmt.Println("empty s:", s)
12 
13   // Set and get
14   s[0] = "a"
15   s[1] = "b"
16   s[2] = "c"
17   fmt.Println("s:", s)
18   fmt.Println("s[2]:", s[2])
19   fmt.Println("len(s):", len(s))
20 
21   // Append
22   s = append(s, "d")
23   s = append(s, "e", "f")
24   fmt.Println("append:", s)
25 }

Running the above program from the terminal gives you:

1 $ go run slices.go
2 empty s: [  ]
3 s: [a b c]
4 s[2]: c
5 len(s): 3
6 append: [a b c d e f]
3.2.5.2 Maps

Maps in Go are similar to hashes or dictionaries in other languages. The maps.go program shows how you can initialize, read, and modify maps:

 1 // maps.go
 2 
 3 package main
 4 
 5 import "fmt"
 6 
 7 func main() {
 8   // Create an empty map with make: `map[key-type]value-type`
 9   m := make(map[string]int)
10 
11   // Set
12   m["a"] = 1
13   m["b"] = 2
14 
15   // Get
16   v1 := m["a"]
17   fmt.Println("m: ", m)
18   fmt.Println("v1", v1)
19 
20   // Removing a key-value pair
21   delete(m, "a")
22   fmt.Println("delete:", m)
23 }

Running the above program from the terminal gives you:

1 $ go run maps.go
2 m: map[a:1 b:2]
3 v1: 1
4 delete: map[b:2]

3.2.6 Loops

3.2.6.1 For

for is Go’s only looping construct:

 1 // for.go
 2 
 3 package main
 4 
 5 import "fmt"
 6 
 7 func main() {
 8   i := 1
 9   for i <= 3 {
10     fmt.Println(i)
11     i = i + 1
12   }
13 
14   for j := 7; j <= 9; j++ {
15     fmt.Println(j)
16   }
17 }

Running the above program from the terminal gives you:

1 $ go run for.go
2 1
3 2
4 3
5 7
6 8
7 9
3.2.6.2 Range

You can use range to iterate over elements in a variety of data structures. The range.go program demonstrates how you can iterate over slices and maps:

 1 // range.go
 2 
 3 package main
 4 
 5 import "fmt"
 6 
 7 func main() {
 8   // Iterating over a slice
 9   nums := []int{2,3,4}
10   sum := 0
11   for index, num := range nums {
12     fmt.Println("current index: ", index)
13     sum += num
14   }
15   fmt.Println("sum:", sum)
16 
17   // Iterating over a map's key/value pairs
18   kvs := map[string]string{"a": "apple", "b": "banana"}
19   for k, v := range kvs {
20       fmt.Printf("%s -> %s\n", k, v)
21   }
22 }

Running the above program from the terminal gives you:

1 $ go run range.go
2 current index: 0
3 current index: 1
4 current index: 2
5 sum: 9
6 a -> apple
7 b -> banana

3.2.7 Functions

Functions in Go accepts parameters of specified types, and returns values of specified types. The functions.go program demonstrates how you can define and call functions in Go:

 1 // functions.go
 2 
 3 package main
 4 
 5 import "fmt"
 6 
 7 // This function takes two ints and returns their sum as an int.
 8 func add(a int, b int) int {
 9   return a + b
10 }
11 
12 func main() {
13   result := add(1, 2)
14   fmt.Println("1+2 = ", result)
15 }

Running the above program from the terminal gives you:

1 $ go run functions.go
2 1+2 = 3

3.2.8 Pointers

Pointers allow you to pass references to values within your program.

You use the * prefix to refer to variables by reference / memory address instead of by value.

The & prefix is used to return the memory address of a variable.

 1 package main
 2 
 3 import "fmt"
 4 
 5 // zeroval has an int parameter, so arguments will be passed to it by value. zero\
 6 val will get a copy of ival distinct from the one in the calling function.
 7 func zeroval(ival int) {
 8   ival = 0
 9 }
10 
11 // zeroptr in contrast has an *int parameter, meaning that it takes an int pointe\
12 r. The *iptr code in the function body then dereferences the pointer from its mem\
13 ory address to the current value at that address. Assigning a value to a derefere\
14 nced pointer changes the value at the referenced address.
15 func zeroptr(iptr *int) {
16   *iptr = 0
17 }
18 
19 func main() {
20     i := 1
21     fmt.Println("initial:", i)
22 
23     zeroval(i)
24     fmt.Println("zeroval:", i)
25 
26     // The &i syntax gives the memory address of i, i.e. a pointer to i
27     zeroptr(&i)
28     fmt.Println("zeroptr:", i)
29 
30     fmt.Println("pointer:", &i)
31 }

Running the above program from the terminal gives you:

1 $ go run pointers.go
2 initial: 1
3 zeroval: 1
4 zeroptr: 0
5 pointer: 0x42131100

Note that zeroval doesn’t change the i in main, but zeroptr does because it has a reference to the memory address for that variable.

3.2.9 Structs

Go Structs are typed collections of fields. They are similar to classes in other languages. Structs are the primary data structure used to encapsulate business logic in Go programs.

The structs.go program shows how you can initialize, read, and modify structs in Go:

 1 // structs.go
 2 
 3 package main
 4 
 5 import "fmt"
 6 
 7 type Person struct {
 8   name  string
 9   age   int
10 }
11 
12 func main() {
13   // Create a new struct
14   fmt.Println(Person{name: "Alice", age: 21})
15 
16   // Omitted fields will be zero-valued
17   fmt.Println(Person{name: "Bob"})
18 
19   // Getters and Setters use dot notation
20   s := Person{name: "Ann", age: 22}
21   fmt.Println(s.name)
22 
23   s.age = 30
24   fmt.Println(s.age)
25 }

Running the above program from the terminal gives you:

1 $ go run structs.go
2 {Alice 21}
3 {Bob 0}
4 Ann
5 30

Go supports methods defined on struct types. The methods.go program demonstrates how you can use method definitions to add behaviour to structs:

 1 // methods.go
 2 
 3 package main
 4 
 5 import "fmt"
 6 
 7 type Rect struct {
 8   width, height int
 9 }
10 
11 // Methods can be defined for either pointer or value receiver types. Here’s an e\
12 xample of a value receiver.
13 func (r Rect) perim() int {
14   return 2*r.width + 2*r.height
15 }
16 
17 // This area method has a receiver type of *rect
18 // You may want to use a pointer receiver type to avoid copying on method calls o\
19 r to allow the method to mutate the receiving struct.
20 func (r *Rect) area() int {
21   return r.width * r.height
22 }
23 
24 func main() {
25   // Here we call the 2 methods defined for our struct.
26   // Go automatically handles conversion between values and pointers for method c\
27 alls.
28   r := &Rect{width: 10, height: 5}
29   fmt.Println("area: ", r.area())
30   fmt.Println("perim:", r.perim())
31 }

Running the above program from the terminal gives you:

1 $ go run methods.go
2 area:  50
3 perim: 30

3.2.10 Interfaces

Go Interfaces are named collections of method signatures.

 1 // interfaces.go
 2 
 3 package main
 4 
 5 import "fmt"
 6 import "math"
 7 
 8 // Here’s a basic interface for geometric shapes.
 9 type Geometry interface {
10   area() float64
11   perim() float64
12 }
13 
14 // For our example we’ll implement this interface on rect and circle types.
15 
16 // To implement an interface in Go, we just need to implement all the methods in \
17 the interface. Here we implement geometry on rects.
18 type Rect struct {
19   width, height float64
20 }
21 
22 func (r Rect) area() float64 {
23   return r.width * r.height
24 }
25 
26 func (r Rect) perim() float64 {
27   return 2*r.width + 2*r.height
28 }
29 
30 // The implementation for circles.
31 type Circle struct {
32     radius float64
33 }
34 
35 func (c Circle) area() float64 {
36     return math.Pi * c.radius * c.radius
37 }
38 
39 func (c Circle) perim() float64 {
40     return 2 * math.Pi * c.radius
41 }
42 
43 // If a variable has an interface type, then we can call methods that are in the \
44 named interface. Heres a generic measure function taking advantage of this to wo\
45 rk on any geometry.
46 func measure(g Geometry) {
47     fmt.Println(g)
48     fmt.Println(g.area())
49     fmt.Println(g.perim())
50 }
51 
52 func main() {
53     r := Rect{width: 3, height: 4}
54     c := Circle{radius: 5}
55 
56     // The circle and rect struct types both implement the geometry interface so \
57 we can use instances of these structs as arguments to measure.
58     measure(r)
59     measure(c)
60 }

Unlike interfaces in other languages, Go interfaces are implicit rather than explicit. You don’t have to annotate a struct to say that it implements an interface. As long as a struct contains methods defined in an interface, that struct implements the interface.

In the interfaces.go program above, you can see how the measure method works for both Circle and Rect, because both structs contains methods defined in the Geometry interface.

Running the above program from the terminal gives you:

1 $ go run interfaces.go
2 {3 4}
3 12
4 14
5 {5}
6 78.53981633974483
7 31.41592653589793

3.2.11 Errors

In Go it’s idiomatic to communicate errors via an explicit, separate return value.

 1 package main
 2 
 3 import "errors"
 4 import "fmt"
 5 
 6 // By convention, errors are the last return value and have type error, a built-i\
 7 n interface.
 8 func f1(arg int) (int, error) {
 9   if args == 42 {
10     return -1, errors.New("42 does not compute")
11   }
12 
13   return arg + 1, nil
14 }

3.2.12 Packages

Nearly every program we’ve seen so far included this line:

1 import "fmt"

fmt is the name of a package that includes a variety of functions related to formatting and output to the screen. Go provides packages as a mechanism for code reuse.

Create a new learn-packages/ directory in the learn-go/ folder:

1 mkdir learn-packages
2 cd learn-packages

Let’s create a new package called math. Create a directory called math/ and in that directory add a new file called math.go:

1 mkdir math
2 touch math.go
 1 // $GOPATH/src/learn-go/learn-packages/math/math.go
 2 
 3 package math
 4 
 5 func Average(xs []float64) float64 {
 6   total := float64(0)
 7   for _, x := range xs {
 8     total += x
 9   }
10   return total / float64(len(xs))
11 }

In our our main.go program, we can import and use our math package:

 1 // $GOPATH/src/learn-go/learn-packages/main.go
 2 
 3 package main
 4 
 5 import "fmt"
 6 import "learn-go/learn-packages/math"
 7 
 8 func main() {
 9   xs := []float64{1,2,3,4}
10   avg := math.Average(xs)
11   fmt.Println(avg)
12 }

3.2.13 Package Management

dep is a dependency management tool for Go.

On MacOS you can install or upgrade to the latest released version with Homebrew:

1 $ brew install dep
2 $ brew upgrade dep

To get started, create a new directory learn-dep/ in your $GOPATH/src:

1 $ mkdir learn-dep
2 $ cd learn-dep

Initialize the project with dep init:

1 $ dep init
2 $ ls
3 Gopkg.lock Gopkg.toml vendor

dep init will create the following:

  • Gopkg.lock is a record of the exact versions of all of the packages that you used for the project.
  • Gopkg.toml is a list of packages your project depends on.
  • vendor/ is the directory where your project’s dependencies are installed.
3.2.13.1 Adding a new dependency

Create a main.go file with the following contents:

1 // main.go
2 
3 package main
4 
5 import "fmt"
6 
7 func main() {
8   fmt.Println("Hello world")
9 }

Let’s say that we want to introduce a new dependency on github.com/pkg/errors. This can be accomplished with one command:

1 $ dep ensure -add github.com/pkg/errors

That’s it!

For detailed usage instructions, check out the official dep docs.

3.3 Go on AWS Lambda

AWS released support for Go on AWS Lambda on January 2018. You can now build Go programs with typed structs representing Lambda event sources and common responses with the aws-lambda-go SDK.

Your Go programs are compiled into a statically-linked binary, bundled up into a Lambda deployment package, and uploaded to AWS Lambda.

3.4 Go Lambda Programming Model

You write code for your Lambda function in one of the languages AWS Lambda supports. Regardless of the language you choose, there is a common pattern to writing code for a Lambda function that includes the following core concepts:

  • Handler – Handler is the function AWS Lambda calls to start execution of your Lambda function. Your handler should process incoming event data and may invoke any other functions/methods in your code.
  • The context object – AWS Lambda also passes a context object to the handler function, which lets you retrieve metadata such as the execution time remaining before AWS Lambda terminates your Lambda function.
  • Logging – Your Lambda function can contain logging statements. AWS Lambda writes these logs to CloudWatch Logs.
  • Exceptions – There are different ways to end a request successfully or to notify AWS Lambda an error occurred during execution. If you invoke the function synchronously, then AWS Lambda forwards the result back to the client.

Your Lambda function code must be written in a stateless style, and have no affinity with the underlying compute infrastructure. Your code should expect local file system access, child processes, and similar artifacts to be limited to the lifetime of the request. Persistent state should be stored in Amazon S3, Amazon DynamoDB, or another cloud storage service.

3.4.1 Lambda Function Handler

A Lambda function written in Go is authored as a Go executable. You write your handler function code by including the github.com/aws/aws-lambda-go/lambda package and a main() function:

 1 package main
 2 
 3 import (
 4   "fmt"
 5   "context"
 6   "github.com/aws/aws-lambda-go/lambda"
 7 )
 8 
 9 type MyEvent struct {
10   Name string `json:"name"`
11 }
12 
13 func HandleRequest(ctx context.Context, name MyEvent) (string, error) {
14   return fmt.Sprintf("Hello %s!", name.Name ), nil
15 }
16 
17 func main() {
18   lambda.Start(HandleRequest)
19 }

Note the following:

  • package main: In Go, the package containing func main() must always be named main.
  • import: Use this to include the libraries your Lambda function requires.
    • context: The Context Object.
    • fmt: The Go Formatting object used to format the return value of your function.
    • github.com/aws/aws-lambda-go/lambda: As mentioned previously, implements the Lambda programming model for Go.
  • func HandleRequest(ctx context.Context, name string) (string, error): This is your Lambda handler signature and includes the code which will be executed. In addition, the parameters included denote the following:
    • ctx context.Context: Provides runtime information for your Lambda function invocation. ctx is the variable you declare to leverage the information available via the the Context Object.
    • name string: An input type with a variable name of name whose value will be returned in the return statement.
    • string error: Returns standard error information.
    • return fmt.Sprintf(“Hello %s!”, name), nil: Simply returns a formatted “Hello” greeting with the name you supplied in the handler signature. nil indicates there were no errors and the function executed successfully.
  • func main(): The entry point that executes your Lambda function code. This is required. By adding lambda.Start(HandleRequest) between func main(){} code brackets, your Lambda function will be executed.
3.4.1.1 Using Structured Types

In the example above, the input type was a simple string. But you can also pass in structured events to your function handler:

 1 package main
 2 
 3 import (
 4         "fmt"
 5         "github.com/aws/aws-lambda-go/lambda"
 6 )
 7 
 8 type MyEvent struct {
 9         Name string 'json:"What is your name?"'
10         Age int     'json:"How old are you?"'
11 }
12 
13 type MyResponse struct {
14         Message string 'json:"Answer:"'
15 }
16 
17 func HandleLambdaEvent(event MyEvent) (MyResponse, error) {
18         return MyResponse{Message: fmt.Sprintf("%s is %d years old!", event.Name,\
19  event.Age)}, nil
20 }
21 
22 func main() {
23         lambda.Start(HandleLambdaEvent)
24 }

Your request would then look like this:

1 {
2     "What is your name?": "Jim",
3     "How old are you?": 33
4 }

And the response would look like this:

1 {
2     "Answer": "Jim is 33 years old!"
3 }

Each AWS event source (API Gateway, DynamoDB, etc.) has its own input/output structs. For example, lambda functions that is triggered by API Gateway events use the events.APIGatewayProxyRequest input struct and events.APIGatewayProxyResponse output struct:

 1 package main
 2 
 3 import (
 4 	"context"
 5 	"fmt"
 6 
 7 	"github.com/aws/aws-lambda-go/events"
 8 	"github.com/aws/aws-lambda-go/lambda"
 9 )
10 
11 func handleRequest(ctx context.Context, request events.APIGatewayProxyRequest) (e\
12 vents.APIGatewayProxyResponse, error) {
13 	fmt.Printf("Body size = %d.\n", len(request.Body))
14 	fmt.Println("Headers:")
15 	for key, value := range request.Headers {
16 		fmt.Printf("    %s: %s\n", key, value)
17 	}
18 
19 	return events.APIGatewayProxyResponse{Body: request.Body, StatusCode: 200}, nil
20 }
21 
22 func main() {
23 	lambda.Start(handleRequest)
24 }

For more information on handling events from AWS event sources, see aws-lambda-go/events.

3.4.2 The Context Object

Lambda functions have access to metadata about their environment and the invocation request such as:

  • How much time is remaining before AWS Lambda terminates your Lambda function.
  • The CloudWatch log group and log stream associated with the executing Lambda function.
  • The AWS request ID returned to the client that invoked the Lambda function.
  • If the Lambda function is invoked through AWS Mobile SDK, you can learn more about the mobile application calling the Lambda function.
  • You can also use the AWS X-Ray SDK for Go to identify critical code paths, trace their performance and capture the data for analysis.
3.4.2.1 Reading function metadata

AWS Lambda provides the above information via the context.Context object that the service passes as a parameter to your Lambda function handler.

You need to import the github.com/aws/aws-lambda-go/lambdacontext library to access the contents of the context.Context object:

 1 package main
 2 
 3 import (
 4         "context"
 5         "log"
 6         "github.com/aws/aws-lambda-go/lambda"
 7         "github.com/aws/aws-lambda-go/lambdacontext"
 8 )
 9 
10 func Handler(ctx context.Context) {
11         lc, _ := lambdacontext.FromContext(ctx)
12         log.Print(lc.AwsRequestId)
13 }
14 
15 func main() {
16         lambda.Start(Handler)
17 }

In the example above, lc is the variable used to consume the information that the context object captured and log.Print(lc.AwsRequestId) prints that information, in this case, the AwsRequestId.

3.4.3 Logging

Your Lambda function can contain logging statements. AWS Lambda writes these logs to CloudWatch.

 1 package main
 2 
 3 import (
 4         "log"
 5         "github.com/aws/aws-lambda-go/lambda"
 6 )
 7 
 8 func HandleRequest() {
 9         log.Print("Hello from Lambda")
10 }
11 
12 func main() {
13         lambda.Start(HandleRequest)
14 }

By importing the log module, Lambda will write additional logging information such as the time stamp. Instead of using the log module, you can use print statements in your code as shown below:

 1 package main
 2 
 3 import (
 4         "fmt"
 5         "github.com/aws/aws-lambda-go/lambda"
 6 )
 7 
 8 func HandleRequest() {
 9         fmt.Print("Hello from Lambda")
10 }
11 
12 func main() {
13         lambda.Start(HandleRequest)
14 }

In this case only the text passed to the print method is sent to CloudWatch. The log entries will not have additional information that the log.Print function returns.

3.4.4 Function Errors

3.4.4.1 Raising custom errors

You can create custom error handling to raise an exception directly from your Lambda function and handle it directly:

 1 package main
 2 
 3 import (
 4         "errors"
 5         "github.com/aws/aws-lambda-go/lambda"
 6 )
 7 
 8 func OnlyErrors() error {
 9         return errors.New("something went wrong!")
10 }
11 
12 func main() {
13         lambda.Start(OnlyErrors)
14 }

When invoked, the above function will return:

1 { "errorMessage": "something went wrong!" }
3.4.4.2 Raising unexpected errors

Lambda functions can fail for reasons beyond your control, such as network outages. In Go, you use panic in response to unexpected errors. If your code panics, Lambda will attempt to capture the error and serialize it into the standard error json format. Lambda will also attempt to insert the value of the panic into the function’s CloudWatch logs.

 1 package main
 2 
 3 import (
 4 	"errors"
 5 
 6 	"github.com/aws/aws-lambda-go/lambda"
 7 )
 8 
 9 func handler(string) (string, error) {
10 	panic(errors.New("Something went wrong"))
11 }
12 
13 func main() {
14 	lambda.Start(handler)
15 }

When invoked, the above function will return the full stack trace due to panic:

 1 {
 2     "errorMessage": "Something went wrong",
 3        "errorType": "errorString",
 4     "stackTrace": [
 5             {
 6             "path": "github.com/aws/aws-lambda-go/lambda/function.go",
 7             "line": 27,
 8             "label": "(*Function).Invoke.function"
 9         },
10  ...
11 
12     ]
13 }

3.4.5 Environment Variables

Use the os.Getenv function to read environment variables:

 1 package main
 2 
 3 import (
 4 	"fmt"
 5 	"os"
 6 	"github.com/aws/aws-lambda-go/lambda"
 7 )
 8 
 9 func main() {
10 	fmt.Printf("Lambda is in the %s region and is on %s", os.Getenv("AWS_REGION"), o\
11 s.Getenv("AWS_EXECUTION_ENV"))
12 
13 }

Lambda configures a list of environment variables by default.

3.5 Summary

The Go we’ve covered so far is more than enough to get you started with building Go applications of AWS Lambda.

3.5.1 Further Learning

To learn more about the Go language, be sure to chec kout the following learning resources: