iteration.md
You can find all the code for this chapter here
To do stuff repeatedly in Go, you'll need for. In Go there are no while, do, until keywords, you can only use for. Which is a good thing!
Let's write a test for a function that repeats a character 5 times.
There's nothing new so far, so try and write it yourself for practice.
package iteration
import "testing"
func TestRepeat(t *testing.T) {
repeated := Repeat("a")
expected := "aaaaa"
if repeated != expected {
t.Errorf("expected %q but got %q", expected, repeated)
}
}
./repeat_test.go:6:14: undefined: Repeat
Keep the discipline! You don't need to know anything new right now to make the test fail properly.
All you need to do right now is enough to make it compile so you can check your test is written well.
package iteration
func Repeat(character string) string {
return ""
}
Isn't it nice to know you already know enough Go to write tests for some basic problems? This means you can now play with the production code as much as you like and know it's behaving as you'd hope.
repeat_test.go:10: expected 'aaaaa' but got ''
The for syntax is very unremarkable and follows most C-like languages.
func Repeat(character string) string {
var repeated string
for i := 0; i < 5; i++ {
repeated = repeated + character
}
return repeated
}
Unlike other languages like C, Java, or JavaScript there are no parentheses surrounding the three components of the for statement and the braces { } are always required. You might wonder what is happening in the row
var repeated string
as we've been using := so far to declare and initializing variables. However, := is simply short hand for both steps. Here we are declaring a string variable only. Hence, the explicit version. We can also use var to declare functions, as we'll see later on.
Run the test and it should pass.
Additional variants of the for loop are described here.
Now it's time to refactor and introduce another construct += assignment operator.
const repeatCount = 5
func Repeat(character string) string {
var repeated string
for i := 0; i < repeatCount; i++ {
repeated += character
}
return repeated
}
+= called "the Add AND assignment operator", adds the right operand to the left operand and assigns the result to left operand. It works with other types like integers.
Writing benchmarks in Go is another first-class feature of the language and it is very similar to writing tests.
func BenchmarkRepeat(b *testing.B) {
for b.Loop() {
Repeat("a")
}
}
You'll see the code is very similar to a test.
The testing.B gives you access to the loop function. Loop() returns true as long as the benchmark should continue running.
When the benchmark code is executed, it measures how long it takes. After Loop() returns false, b.N contains the total number of iterations that ran.
The number of times the code is run shouldn't matter to you, the framework will determine what is a "good" value for that to let you have some decent results.
To run the benchmarks do go test -bench=. (or if you're in Windows Powershell go test -bench=".")
goos: darwin
goarch: amd64
pkg: github.com/quii/learn-go-with-tests/for/v4
10000000 136 ns/op
PASS
What 136 ns/op means is our function takes on average 136 nanoseconds to run (on my computer). Which is pretty ok! To test this it ran it 10000000 times.
Note: By default benchmarks are run sequentially.
Only the body of the loop is timed; it automatically excludes setup and cleanup code from benchmark timing. A typical benchmark is structured like:
func Benchmark(b *testing.B) {
//... setup ...
for b.Loop() {
//... code to measure ...
}
//... cleanup ...
}
Strings in Go are immutable, meaning every concatenation, such as in our Repeat function, involves copying memory to accommodate the new string. This impacts performance, particularly during heavy string concatenation.
The standard library provides the strings.BuilderstringsBuilder type which minimizes memory copying.
It implements a WriteString method which we can use to concatenate strings:
const repeatCount = 5
func Repeat(character string) string {
var repeated strings.Builder
for i := 0; i < repeatCount; i++ {
repeated.WriteString(character)
}
return repeated.String()
}
Note: We have to call the String method to retrieve the final result.
We can use BenchmarkRepeat to confirm that strings.Builder significantly improves performance.
Run go test -bench=. -benchmem:
goos: darwin
goarch: amd64
pkg: github.com/quii/learn-go-with-tests/for/v4
10000000 25.70 ns/op 8 B/op 1 allocs/op
PASS
The -benchmem flag reports information about memory allocations:
B/op: the number of bytes allocated per iterationallocs/op: the number of memory allocations per iterationExampleRepeat to document your functionfor