The Go standard library makes concatenating strings easy. Concatenation is just a fancy word for adding strings together to make a larger string. For example, if we concatenate "hello"
, " "
and "world"
we’d get "hello world"
.
The built-in fmt.Sprintf
function takes a format and a variadic list of interfaces as input.
func Sprintf(format string, a ...interface{}) string
The formatting option lets us template out how the final string will look, then we can add inputs that will be interpolated into the string.
s := fmt.Sprintf("%v has been subscribed since %v.\n", user.Name, user.CreatedAt)
%v
is a simple token that will be replaced by the default format of whatever the given arguments are. In our case, it was a string
and a time.Time. Check out the documentation for all the formatting options.
Efficient string concatenation ๐
Go 1.10+ released the awesome strings.Builder
type, which lets us more efficiently build strings. Because it minimizes memory copying, strings.Builder
is a high-performance option when it comes to doing bulk string concatenation.
Quickly writing a user list - example ๐
First, let’s create an empty builder.
var builder strings.Builder
Next, let’s add a title string to our list.
b.WriteString("user list\n")
Now we’ll iterate from 0-9, and for each number write a line containing “user #{i}
”. Because strings.Builder
implements the io.Writer
interface, we can use the standard fmt.Fprintf
function.
for i := 0; i < 10; i++ {
fmt.Fprintf(&b, "user #%d\n", i)
}
To actually print the full string we can use the String()
method.
fmt.Println(b.String())
Full code:
package main
import (
"fmt"
"strings"
)
func main() {
var b strings.Builder
b.WriteString("user list\n")
for i := 0; i < 10; i++ {
fmt.Fprintf(&b, "user #%d\n", i)
}
fmt.Println(b.String())
}
Preallocation for more speed ๐
If you really want to speed up your string building, and you already know the size of your final string, you can use the builder’s Grow()
method to preallocate the size of the buffer. This saves your code from needing to grow the memory dynamically.
// Grow grows b's capacity, if necessary, to guarantee space for another n bytes.
// After Grow(n), at least n bytes can be written to b without another allocation.
// If n is negative, Grow panics.
func (b *Builder) Grow(n int)
So to preallocate for our example we could do the following.
package main
import (
"fmt"
"strings"
)
func main() {
var b strings.Builder
b.Grow(90) // we will be writing 90 bytes
b.WriteString("user list\n")
for i := 0; i < 10; i++ {
fmt.Fprintf(&b, "user #%d\n", i)
}
fmt.Println(b.String())
}