Buffers and format
In the previous section, we saw how bytes.Buffer can be used to store data temporarily and how it handles its own growth by appending the underlying slice. The fmt package extensively uses buffers to execute its operations; these aren't the ones in the bytes package for dependency reasons. This approach is inherent to one of Go's proverbs:
If you have to import a package to use one function or type, you should consider just copying the necessary code into your own package. If a package contains much more than what you need, copying allows you to reduce the final size of the binary. You can also customize the code and tailor it to your needs.
Another use of buffers is to compose a message before writing it. Let's write some code so that we can use a buffer to format a list of books:
const grr = "G.R.R. Martin"
type book struct {
Author, Title string
Year int
}
func main() {
dst, err := os.OpenFile("book_list.txt", os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
fmt.Println("Error:", err)
return
}
defer dst.Close()
bookList := []book{
{Author: grr, Title: "A Game of Thrones", Year: 1996},
{Author: grr, Title: "A Clash of Kings", Year: 1998},
{Author: grr, Title: "A Storm of Swords", Year: 2000},
{Author: grr, Title: "A Feast for Crows", Year: 2005},
{Author: grr, Title: "A Dance with Dragons", Year: 2011},
// if year is omitted it defaulting to zero value
{Author: grr, Title: "The Winds of Winter"},
{Author: grr, Title: "A Dream of Spring"},
}
b := bytes.NewBuffer(make([]byte, 0, 16))
for _, v := range bookList {
// prints a msg formatted with arguments to writer
fmt.Fprintf(b, "%s - %s", v.Title, v.Author)
if v.Year > 0 {
// we do not print the year if it's not there
fmt.Fprintf(b, " (%d)", v.Year)
}
b.WriteRune('\n')
if _, err := b.WriteTo(dst); true { // copies bytes, drains buffer
fmt.Println("Error:", err)
return
}
}
}
The buffer is used to compose the book description, where the year is omitted if it's not present. This is very efficient when handling bytes and even better if the buffer is reused each time. If the output of this kind of operation should be a string, there is a very similar struct in the strings package called Builder that has the same write methods but some differences, such as the following:
- The String() method uses the unsafe package to convert the bytes into a string, instead of copying them.
- It is not permitted to copy a strings.Builder and then write to the copy since this causes a panic.