Go is a systems programming language that first appeared in 2009. Most notably:
Here is the language rationale from the Go Programming Language FAQ:
Go is an attempt to combine the ease of programming of an interpreted, dynamically typed language with the efficiency and safety of a statically typed, compiled language. It also aims to be modern, with support for networked and multicore computing. Finally, it is intended to be fast: it should take at most a few seconds to build a large executable on a single computer. To meet these goals required addressing a number of linguistic issues: an expressive but lightweight type system; concurrency and garbage collection; rigid dependency specification; and so on. These cannot be addressed well by libraries or tools; a new language was called for.
Go has an official definition.
Like any modern language, it is currently evolving. If interested, check out the version history.
Let's start with Hello, world:
package main
import "fmt"
func main() {
fmt.Println("Hello, world")
}
To build and run:
$ go run hello.go Hello, world
or you can do it the long way:
$ go build hello.go && ./hello Hello, world
And another little script:
package main
import "fmt"
import "os"
import "strconv"
func main() {
n, error := strconv.Atoi(os.Args[1])
if error != nil {
fmt.Println("A single integer commandline argument is required")
} else {
a, b := 0, 1
for b <= n {
fmt.Println(b)
a, b = b, a+b
}
}
}
$ go run fib.go 100 1 1 2 3 5 8 13 21 34 55 89 $ go run fib.go dog A single integer commandline argument is required
And another:
package main
import "fmt"
func main() {
for c := 1; c <= 100; c++ {
for b := 1; b <= c; b++ {
for a := 1; a <= b; a++ {
if a*a+b*b == c*c {
fmt.Printf("(%d,%d,%d)\n", a, b, c)
}
}
}
}
}
Now, here are a few more sample programs. You can browse the list just to see what Go “looks like” rather than to learn from the examples.
You should spend the time to take the interactive tour at gloang.org. It is very good.
Also, go through the excellent Go By Example. It isn't interactive like Google's Tour of Go; however, the annotations beside the example code are really nice.
You can also experiment a little using the Go Playground. There's no actual REPL for Go, but things compile so fast it probably doesn't matter too much.
Don’t miss the main Go page. You can find everything from here. After watching any intro videos and taking any tours go to the doc page and go through the items in the "Learning Go" section.
When ready to learn everything, see the great portal Awesome Go.
Go is known for what it leaves out as much as for what it has. You’ll notice the language is quite small.
There are very few keywords compared to most mainstream languages. Just these:
break
case
chan
const
continue
default
defer
else
fallthrough
for
func
go
goto
if
import
interface
map
package
range
return
select
struct
switch
type
var
There aren’t that many operators, either. While C++ has over 50, Go has less that half that. Here is the operator precedence chart, from highest precedence to lowest:
Operators | Associativity | Description |
---|---|---|
+ - ^ !
| — | unary plus, negation, bitwise complement, logical not |
* / % << >> & &^
| L | multiplication, division, modulo, left shift, right shift, binary and, binary and-not |
+ - | ^
| L | addition, subtraction, binary or, binary xor |
== != < <= > >=
| L | equals, not equals, less, less or equal, greater, greater or equal |
&&
| L | short-circuit and |
||
| L | short-circuit or |
SemicolonsWhile semicolons terminate many syntactic constructs, you don’t have to type them into your source code. Go will automatically insert a semicolon into the token stream at the end of a non-blank line if the last token on the line is an identifier, integer literal, floating-point literal, imaginary literal, rune literal, string literal,
break
,continue
,fallthrough
,return
,++
,--
,)
,]
, or}
.
For complete details of Go syntax, see the Go Programming Language Specification.
These identifiers come already declared for you:
any
bool
byte
comparable
complex64
complex128
error
float32
float64
int
int8
int16
int32
int64
rune
string
uint
uint8
uint16
uint32
uint64
uintptr
true
false
iota
nil
append
cap
clear
close
complex
copy
delete
imag
len
make
max
min
new
panic
print
println
real
recover
Go programs must run in a package (they start in main
) and there are a bunch of standard
packages that you can import from.
Please browse the list of standard packages to see what's available.
Four of them are featured here:
package main
import (
"fmt"
"math/rand"
"strings"
"time"
)
func main() {
fmt.Printf("%q\n", strings.Split("a:bcd:ef", ":"))
r := rand.New(rand.NewSource(time.Now().UnixNano()))
fmt.Println(r.Int31())
}
When writing function signatures, you must supply the argument type(s) and (if present) the return type(s). You can name the return value(s) too. Variadic functions are supported by prefixing the parameter type with ...
. This will roll up the arguments into a single slice object.
package main
import "fmt"
func add(x int, y int) int {
return x + y
}
// Multiple parameters of the same type can be shortened to
func divide(x, y int) int {
return x / y
}
// Multiple return values
func swap(x, y string) (string, string) {
return y, x
}
// Named return values
func divmod(x, y int) (quo, rem int) {
quo = x / y
rem = x % y
return
}
// Variadic functions (aribtrary number of arguments)
func sumOfFloats(a ...float64) float64 {
total := 0.0
for _, x := range a {
total += x
}
return total
}
func main() {
fmt.Println(add(1, 2), divide(33, 10))
one, two := swap("ho", "hi")
three, four := divmod(97, 25)
fmt.Println(one, two, three, four)
fmt.Println(sumOfFloats(8, 4.3, -2, 11.9))
}
Variables are declared with var
. There is some degree of type inference. There is a default initial value for each type. This is rather cool, right? Inside a function, a short variable declaration is allowed (but never outside a function).
package main
import "fmt"
var x int = 10
var y int // value is 0
var z = 12 // type inferred to be int
var a, b, c bool // multiple vars declared at once
var p, q float64 = 8.9, 2.3 // multiple vars with initializers
var (
message = "O noes"
start complex128
)
func main() {
var s rune = '$' // nothing special here
t := "hello" // short var declaration, same as var t string = "hello"
fmt.Println(x, y, z, a, b, c, p, q, s, t, message, start)
}
Go has a rich set of static types. Here are the predeclared types:
Type | Description | Zero value |
---|---|---|
bool
| Contains only the values true and false |
false |
uint8 |
Unsigned 8-bit integers (0 to 255) | 0 |
uint16 |
Unsigned 16-bit integers (0 to 65535) | 0 |
uint32 |
Unsigned 32-bit integers (0 to 4294967295) | 0 |
uint64 |
Unsigned 64-bit integers (0 to 18446744073709551615) | 0 |
int8 |
Signed 8-bit integers (-128 to 127) | 0 |
int16 |
Signed 16-bit integers (-32768 to 32767) | 0 |
int32 |
Signed 32-bit integers (-2147483648 to 2147483647) | 0 |
int64 |
Signed 64-bit integers (-9223372036854775808 to 9223372036854775807) | 0 |
float32 |
IEEE-754 32-bit floating-point numbers | 0 |
float64 |
IEEE-754 64-bit floating-point numbers | 0 |
complex64 |
Complex numbers with float32 real and imaginary parts |
(0+0i) |
complex128 |
Complex numbers with float64 real and imaginary parts |
(0+0i) |
byte |
Alias for uint8 |
0 |
rune |
Alias for int32 |
0 |
uint |
IMPLEMENTATION-SPECIFIC either 32 or 64 bits | 0 |
int |
IMPLEMENTATION-SPECIFIC same size as uint |
0 |
uintptr |
IMPLEMENTATION-SPECIFIC an unsigned integer large enough to store the uninterpreted bits of a pointer value | 0x0 |
string |
Immutable sequences of bytes | "" |
error |
Defined as: type error interface {Error() string} |
nil |
any |
Alias for interface{} , the empty interface |
nil |
comparable |
Interface denoting the set of all non-interface types whose instances are strictly comparable. This type can only be used in the context of a type constraint. | nil |
There are facilities for you to create new types:
Kind of type | Examples | Is nil a value? | Zero Value |
---|---|---|---|
Array | [7]int | No | An array of the defined length with all elements set ot their zero values |
Slice | []int | Yes | nil |
Struct | struct {X int; Y int} | No | Struct with the defined shape having all fields with their zero values |
Map | map[string]int | Yes | nil |
Function | func(int)int | Yes | nil |
Channel | chan int | Yes | nil |
Pointer | *int | Yes | nil |
Interface | interface {Save()} | Yes | nil |
Numbers, booleans, runes, strings, arrays, and structs are value types. They are always copied when assigned and passed and returned.
Pointers, slices, maps, channels, functions, and interfaces are reference types. They are not copied when assigned and passed and returned. Sadly, the value nil
is a value of each reference type. This means that like Java, the static and dynamic types are not quite synced up, meaning you may need lots of runtime checking for nil
.
package main
func main() {
// Arrays are value types
a := [3]int{1, 2, 3}
b := a
b[1] = 1000
println(a[1], b[1]) // 2 1000
// Structs are value types
s := struct {
x int
y int
}{1, 2}
t := s
t.y = 1000
println(s.y, t.y) // 2 1000
// Maps are reference types
m := map[string]int{"x": 1, "y": 2}
n := m
n["y"] = 1000
println(m["y"], n["y"]) // 1000 1000
m = nil
println(m["y"]) // 0 (safe to read from nil map)
// But writing to a nil map will cause a panic
// Slices are reference types
x := []int{1, 2, 3}
y := x
y[1] = 1000
println(x[1], y[1]) // 1000 1000
x = nil
println(len(x)) // 0 (length of nil slice)
// But writing to a nil slice will cause a panic
}
Note the subtle differences between the empty slice and the nil slice and between the empty map and the nil map.
Read about if and switch at Go By Example.
Write loops with the for
statement. There is no while
or repeat
keyword. There is only for
. But there are lots of forms. The following table lists most, but not all, of them:
Form | Description |
---|---|
for {...} | infinite loop, runs until a break, return, or panic |
for x < 10 {...} | like “while x < 10” in other languages |
for i := 0; i < 10; {...} for i < 10; i+=2 {...} for i := 0; i < 10; i+=2 {...} | iterates while a condition is true, with an optional statement before the whole loop and an optional statement at the end of each iteration. If the initial statement is a declaration, the scope the variable is local to the loop. |
for range 10 {...} | does the body 10 times |
for i := range 10 {...} | does the body 10 times, with a variable i taking on the values 0, 1, ..., 9. The variable i is declared at the beginning of the statement and is local to the block.
|
for range aSlice {...} for i := range aSlice {...} for i, v := range aSlice {...} for _, v := range aSlice {...}
|
Iterates through a slice, optionally binding i and/or v to the index and value of each element, respectively. |
for range aString {...} for i := range aString {...} for i, c := range aString {...} for _, c := range aString {...}
|
Iterates through the runes of a string, optionally binding i and/or c to the index and rune of each element, respectively. |
for range aMap {...} for k := range aMap {...} for k, v := range aMap {...} for _, v := range aMap {...}
|
Iterates through the key-value pairs of a map, optionally binding k and/or v to the key and value of each element, respectively. |
for range aChannel {...} for e := range aChannel {...} |
Receives values on a channel until closed, optionally binding e to the value received. |
Note that just as with the if
statement, variables declared at the top of the statement,
before the block, are local to the block.
Loops have break
and continue
, too. Loops can be exited early with break
, return
, or a panic.
Read about the for statement at Go By Example.
The syntax is pretty conventional here: Use &
to make a pointer to something, and use *
to dererence the pointer.
package main
import "fmt"
func uselessTriple(x int) {
x *= 3 // Yep, this is completely useless
}
func reallyTriple(x *int) {
*x *= 3 // Changes the referent of the argument
}
func main() {
x := 3
y := &x // Perfectly okay to point to a local variable
uselessTriple(x) // x does not change
fmt.Println(x) // Prints 3
reallyTriple(y) // You should draw a picture of what's going on
fmt.Println(x) // Yep, prints 9
}
Read about structs at Go By Example.
Read about arrays and slices at Go By Example.
Read about maps at Go By Example.
Functions are first-class citizens in Go. You can assign them to variables, pass them as arguments, and return them from other functions. Here is a simple example:
package main
import "fmt"
func twice(f func(int) int, x int) int {
return f(f(x))
}
func main() {
square := func(x int) int { return x * x }
fmt.Println(twice(square, 5))
fmt.Println(twice(func(x int) int { return x * 2 }, 5))
}
Functions can be closures too. You can write your own versions of map, filter, reduce, and friends, but such things are rare in the kind of applications Go is used for. Even one of the Go creators, Rob Pike, says you should just use for loops for this kind of thing.
A method is a function with a receiver. Generally the receiver is a pointer but doesn't have to be. By making it a pointer you allow the method to be a mutator, and you don't incur cost of a copy. Let’s look at two examples. The first is a completely immutable Quaternion type:
package quaternions
import (
"errors"
"fmt"
"math"
"strings"
)
type Quaternion struct {
A, B, C, D float64
}
var (
Zero = Quaternion{0.0, 0.0, 0.0, 0.0}
I = Quaternion{0.0, 1.0, 0.0, 0.0}
J = Quaternion{0.0, 0.0, 1.0, 0.0}
K = Quaternion{0.0, 0.0, 0.0, 1.0}
)
func NewQuaternion(a, b, c, d float64) (Quaternion, error) {
if math.IsNaN(a) || math.IsNaN(b) || math.IsNaN(c) || math.IsNaN(d) {
return Quaternion{}, errors.New("coefficients cannot be NaN")
}
return Quaternion{a, b, c, d}, nil
}
func (q Quaternion) Add(other Quaternion) Quaternion {
return Quaternion{
A: q.A + other.A,
B: q.B + other.B,
C: q.C + other.C,
D: q.D + other.D,
}
}
func (q Quaternion) Multiply(other Quaternion) Quaternion {
return Quaternion{
A: other.A*q.A - other.B*q.B - other.C*q.C - other.D*q.D,
B: other.A*q.B + other.B*q.A - other.C*q.D + other.D*q.C,
C: other.A*q.C + other.B*q.D + other.C*q.A - other.D*q.B,
D: other.A*q.D - other.B*q.C + other.C*q.B + other.D*q.A,
}
}
func (q Quaternion) Conjugate() Quaternion {
return Quaternion{q.A, -q.B, -q.C, -q.D}
}
func (q Quaternion) Coefficients() []float64 {
return []float64{q.A, q.B, q.C, q.D}
}
func (q Quaternion) String() string {
coefficients := q.Coefficients()
units := []string{"", "i", "j", "k"}
var builder strings.Builder
for i, c := range coefficients {
if c != 0 {
if c < 0 {
builder.WriteString("-")
} else if builder.Len() > 0 {
builder.WriteString("+")
}
if math.Abs(c) != 1.0 || units[i] == "" {
builder.WriteString(fmt.Sprintf("%g", math.Abs(c)))
}
builder.WriteString(units[i])
}
}
if builder.Len() == 0 {
return "0"
}
return builder.String()
}
The second is a mutable stack. The receiver is always a pointer:
package stacks
import "fmt"
type Stack[T any] struct {
elements []T
}
func NewStack[T any]() *Stack[T] {
return &Stack[T]{}
}
func (s *Stack[T]) Push(value T) {
s.elements = append(s.elements, value)
}
func (s *Stack[T]) Pop() (T, error) {
if len(s.elements) == 0 {
var zero T
return zero, fmt.Errorf("stack is empty")
}
top := s.elements[len(s.elements)-1]
s.elements = s.elements[:len(s.elements)-1]
return top, nil
}
func (s *Stack[T]) Size() int {
return len(s.elements)
}
func (s *Stack[T]) IsEmpty() bool {
return len(s.elements) == 0
}
An interface is a collection of method signatures. You don't have to say that a type impements an interface, the compiler will check that all the methods are implemented when you try to assign (or pass) an object to a variable that is given an interface for a type.
package main
import (
"fmt"
"math"
)
type Shape interface {
perimeter() float64
area() float64
}
type Circle struct {
radius float64
}
func (c Circle) perimeter() float64 {
return 2.0 * math.Pi * c.radius
}
func (c Circle) area() float64 {
return math.Pi * c.radius * c.radius
}
type Rectangle struct {
length, width float64
}
func (r Rectangle) perimeter() float64 {
return 2.0 * (r.width + r.length)
}
func (r Rectangle) area() float64 {
return r.width * r.length
}
func showDetails(s Shape) {
fmt.Printf("%T perimeter is %g and area is %g\n", s, s.perimeter(), s.area())
}
func main() {
showDetails(Rectangle{5.5, 20.3})
showDetails(Circle{10})
}
$ go run shapes.go main.Rectangle perimeter is 51.6 and area is 111.65 main.Circle perimeter is 62.83185307179586 and area is 314.1592653589793
Rather than throwing exceptions, returning error codes, or returning result enums (say, like Swift), the convention in Go is for failable functions to simply return two values: the first is the value that should be returned on success, and the second is either nil
on success or a value of a type implementing error
on failure.
There are a few examples of this above.
Go has a defer
statement that schedules a function call to be run after the function it is in returns. This is useful for cleanup tasks, like closing a file or unlocking a mutex. Read about defer at Go By Example.
A panic is a run time error that normally is fatal and nonrecoverable. It is similar to throwing an exception in other languages. In some rare cases, you can recover from a panic using the recover
function. Read about panic at Go By Example.
Go’s concurrency features are awesome. Go was designed with concurrent applications in mind. It’s helpful organize your study of Go concurrency around three big themes:
A goroutine is essentially a lightweight thread
For message passing and synchronization between goroutines
Mutexes, wait groups, atomics, condition variables, and more
All code runs on a goroutine. All of the examples above have just one goroutine, the main one. The entire program stops when the main goroutine stops, so you almost always need a way for the main goroutine to wait for the others to finish. One approach is to use a channel:
package main
import (
"fmt"
"time"
)
var done = make(chan bool)
func printChars(c rune, n int) {
for i := 0; i < n; i++ {
fmt.Printf("%c", c)
time.Sleep(time.Millisecond)
}
done <- true
}
func main() {
go printChars('0', 500)
go printChars('1', 500)
<-done
<-done
fmt.Println()
}
Another is to use a wait group:
package main
import (
"fmt"
"sync"
"time"
)
func printChars(c rune, n int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < n; i++ {
fmt.Printf("%c", c)
time.Sleep(time.Millisecond)
}
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go printChars('0', 500, &wg)
go printChars('1', 500, &wg)
wg.Wait()
fmt.Println()
}
Here are the things to know about channels:
chan int
, chan struct{}
.<-chan T
), write-only (chan<- T
), or read-write (chan T
).make(chan int)
; buffered: make(chan int, 10)
x
to channel ch
, say ch <- x
ch
, say x := <-ch
In an unbuffered channel, the sender and receiver synchronize on a single value and they block for each other. (This autosync can be exploited to not require locks or condition variables in many cases.) We saw an example above.
A buffered channel has a capacity. Senders only block if the channel is full; if not full, they write immediately and proceed, even if there is no receiver is ready! Receivers only block if the channel is empty:
TODO Buffered channel example
Here is quick comparison of unbuffered and buffered channels:
Feature | Unbuffered Channel | Buffered Channel |
---|---|---|
Capacity | Zero capacity | Fixed, user-defined capacity |
Sending | Blocks until received | Blocks only if buffer is full |
Receiving | Blocks until sent | Blocks only if buffer is empty |
Synchronization | Strict, synchronous rendezvous | Asynchronous (within buffer capacity) |
Use Case | Strict synchronization, handshaking, rendezvous | Decoupling, producer-consumer patterns, rate limiting, asynchronous communication, fire and forget |
As channels are a form of message passing, Go has ways to do balking and timeout sends, and you can also write servers that can timeout or balk on receives. Here is an example showing the different kinds of sends (blocking, nonblocking, and timeout):
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var ch = make(chan struct{})
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
// Non blocking send, receiver will not be ready
select {
case ch <- struct{}{}:
fmt.Println("Receiver was immediately ready, message sent")
default:
fmt.Println("Receiver was not immediately ready")
}
// Two second timeout, receiver will not be ready
select {
case ch <- struct{}{}:
fmt.Println("Receiver ready within 2 seconds, message sent")
case <-time.After(2 * time.Second):
fmt.Println("Timeout after 2 seconds")
}
// Ten second timeout, receiver will be ready
select {
case ch <- struct{}{}:
fmt.Println("Receiver ready within 10 seconds, message sent")
case <-time.After(10 * time.Second):
fmt.Println("Timeout after 10 seconds")
}
// This nonblocking send will successfully send a message
select {
case ch <- struct{}{}:
fmt.Println("Receiver was immediately ready, message sent")
default:
fmt.Println("Receiver was not immediately ready")
}
// Blocking send, will eventually be serviced
ch <- struct{}{}
fmt.Println("Blocking send completed, message sent")
}()
// Enough time for the first two sends to find the receiver not ready
time.Sleep(3 * time.Second)
// Service the 10s timeout then the next non-blocking send
<-ch
<-ch
// Wait a bit before servicing the final blocking send
time.Sleep(5 * time.Second)
<-ch
wg.Wait()
}
The select statement is also used for nonblocking and timed receives. But it can also be multi-way, servicing any available channel:
TODO server-side analog of the previous example
Go By Example has good examples of the select statement, timeouts, and nonblocking operations.
In some cases, a sender may wish to close a channel. This means you won’t be sending anything else on the channel (if you do, it will cause a panic). Closing a channel allows receivers to detect this and stop waiting for new values. Here is how a receiver can detect a close:
TODO Close detection
A receiver can also iterate over a channel using the for
statement, which will stop when the channel is closed:
TODO For range over channel
When programming with channels, cleaning up and closing down channels gracefully is quite important. Here’s a fairly comprehensive article covering many issues and tactics for managing channels cleanly.
Go provides timers and tickers for executing jobs once or repeatedly, respectively, in the future. Creating a timer or ticker creates a channel that you can read from when the next time is reached (as close as possible) and the date becomes ready. Read about timers and tickers at Go By Example.
If you have an application with shared data that multiple goroutines are reading and writing concurrently, you might have data races. There are a few things you can employ to avoid them:
sync.Mutex
and sync.RWMutex
: traditional lock-based synchronization (Example)sync.Cond
: condition variables for signaling between goroutinessync.Once
: for running a function exactly once, regardless of how many goroutines are calling itsync.atomic
package provide fine-grained atomic operations for single variables (Example)Here are some more thorough overviews and lessons on Go concurrency:
You might also be interested in articles from the Go blog:
Go tests come in two forms—traditional unit tests and testable examples. The testable examples are awesome. They say they are best for simple cases only, but I dunno, I use them all the time. Here’s the test for the quaternions package above:
package quaternions
import "fmt"
func ExampleQuaternion() {
q := Quaternion{3.5, 2.25, -100.0, -1.25}
fmt.Println(q.A, q.B, q.C, q.D)
fmt.Println(q.Coefficients())
// Output: 3.5 2.25 -100 -1.25
// [3.5 2.25 -100 -1.25]
}
func ExampleArithmetic() {
q1 := Quaternion{1.0, 3.0, 5.0, 2.0};
q2 := Quaternion{-2.0, 2.0, 8.0, -1.0};
q3 := Quaternion{-1.0, 5.0, 13.0, 1.0};
q4 := Quaternion{-46.0, -25.0, 5.0, 9.0};
fmt.Println(q1.Add(q2) == q3)
fmt.Println(q1.Multiply(q2) == q4)
fmt.Println(q1.Add(Zero) == q1)
fmt.Println(q1.Multiply(Zero) == Zero)
fmt.Println(I.Multiply(J) == K)
fmt.Println(J.Multiply(K) == I)
fmt.Println(J.Add(I) == Quaternion{0.0, 1.0, 1.0, 0.0})
// Output: true
// true
// true
// true
// true
// true
// true
}
func ExampleStrings() {
fmt.Println(Zero)
fmt.Println(J)
fmt.Println(K.Conjugate())
fmt.Println(J.Conjugate().Multiply(Quaternion{2.0, 0.0, 0.0, 0.0}))
fmt.Println(J.Add(K))
fmt.Println(Quaternion{0.0, -1.0, 0.0, 2.25})
fmt.Println(Quaternion{-20.0, -1.75, 13.0, -2.25})
fmt.Println(Quaternion{-1.0, -2.0, 0.0, 0.0})
fmt.Println(Quaternion{1.0, 0.0, -2.0, 5.0})
// Output: 0
// j
// -k
// -2j
// j+k
// -i+2.25k
// -20-1.75i+13j-2.25k
// -1-2i
// 1-2j+5k
}
Run like so:
$ go test quaternion.go quaternion_test.go ok
For traditional unit tests, you have test functions accepting pointers to a *testing.T
:
package stats
import "errors"
func Average(a []float64) (float64, error) {
if len(a) == 0 {
return 0, errors.New("No average for empty collection")
}
total := 0.0
for _, x := range a {
total += x
}
return total / float64(len(a)), nil
}
And here's the test:
package stats
import "testing"
func TestAverage(t *testing.T) {
average, error := Average([]float64{10.0, 1.0, 4.0})
if average != 5.0 && error != nil {
t.Errorf("Average of [10, 1, 4] should be 5")
}
_, error = Average([]float64{})
if error == nil {
t.Errorf("Average of empty slice should return an error")
}
}
To test:
$ go test stats.go stats_test.go ok
This works, and is good enough for school, but in general, testing should be part of building your applications according to the standard Go project layout, which you'll learn about in the video in the next section.
Go has an awesome ecosystem for building and managing massive software projects.
Here is how you can build an app using your own packages.