splash image

April 18, 2021

Getting started with Go pointers

This is part 2 of my experience as a new user of Go, focusing on the quirks and gotchas of pointers. For installation, testing, and packages, see Getting started with Go.

If you'd like to follow along, and try out out the code in this article, all you need is the Go playground to run the examples.

Pointers

The shortscale package which I covered last time, uses a string Builder. Here is the example from the Builder docs.

package main

import (
	"fmt"
	"strings"
)

func main() {
	var b strings.Builder
	for i := 3; i >= 1; i-- {
		fmt.Fprintf(&b, "%d...", i)
	}
	b.WriteString("ignition")
	fmt.Println(b.String())
}

Notice that var b is an instance of the Builder. When you run the code, it will output: 3...2...1...ignition.

Pointer receiver methods and interfaces

The first argument to fmt.Fprintf is &b, a pointer to b. This is necessary, because fmt.Fprintf expects an io.Writer interface.

type Writer interface {
	Write(p []byte) (n int, err error)
}

The Builder.Write method matches the io.Writer interface. Notice the pointer syntax in the method receiver after the func keyword.

func (b *Builder) Write(p []byte) (int, error)

I was tempted to replace Fprintf(&b, ...) with Fprintf(b, ...), to make it more consistent with the b.WriteString() and b.String() further down, but doing this causes the compiler to complain:

"cannot use b (type strings.Builder) as type io.Writer in argument to fmt.Fprintf: strings.Builder does not implement io.Writer (Write method has pointer receiver)"

Value vs. pointer function arguments

What if, instead of depending on the Writer interface, we called our own write() function?

func main() {
	var b strings.Builder
	for i := 3; i >= 1; i-- {
		write(b, fmt.Sprintf("%d...", i))
	}
	b.WriteString("ignition")
	fmt.Println(b.String())
}

func write(b strings.Builder, s string) {
	b.WriteString(s)
}

Running the code above in the example sandbox outputs just the word ignition.

The 3 calls to write(b) do not modify the builder declared at the top.

This makes sense, because passing a struct to a function copies the struct value.

To fix this, we have to use a pointer to pass the struct by reference, and we have to invoke the function with write(&b, ...). This works, but it doesn't make the code any more consistent.

func main() {
	var b strings.Builder
	for i := 3; i >= 1; i-- {
		write(&b, fmt.Sprintf("%d...", i))
	}
	b.WriteString("ignition")
	fmt.Println(b.String())
}

func write(b *strings.Builder, s string) {
	b.WriteString(s)
}

Why do the method calls work?

Why are we allowed to use b instead of &b in front of b.WriteString and b.String? This is explained in the tour as well.

"...even though v is a value and not a pointer, the method with the pointer receiver is called automatically. That is, as a convenience, Go interprets the statement v.Scale(5) as (&v).Scale(5) since the Scale method has a pointer receiver."

Start with a pointer

If all this mixing of values and pointers feels inconsistent, why not start with a pointer from the beginning?

The following code will compile just fine, but can you tell what's wrong with it?

func main() {
	var b *strings.Builder
	for i := 3; i >= 1; i-- {
		fmt.Fprintf(b, "%d...", i)
	}
	b.WriteString("ignition")
	fmt.Println(b.String())
}

The declaration above results in a nil pointer panic at run time, because b is uninitialized.

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x4991c7]

Create the Builder with new()

Here is one way to initialize a pointer so that it references a new Builder.

func main() {
	b := new(strings.Builder)
	for i := 3; i >= 1; i-- {
		fmt.Fprintf(b, "%d...", i)
	}
	b.WriteString("ignition")
	fmt.Println(b.String())
}

new(strings.Builder) returns a pointer to a freshly allocated Builder, which we can use for both functions and pointer receiver methods. This is the pattern which I now use in shortscale-go.

An alternative, which does the same thing, is the more explicit struct literal shown below.

func main() {
	b := &strings.Builder{}
	...

There's no avoiding pointers in Go.
Learn the quirks and the gotchas today.
✨ Keep learning! ✨

To leave a comment
please visit dev.to/jldec

debug

user: anonymous

{
  "path": "/blog/getting-started-with-go-part-2-pointers",
  "attrs": {
    "title": "Getting started with Go pointers",
    "splash": {
      "image": "/images/ladybug.jpg"
    },
    "date": "2021-04-18",
    "layout": "BlogPostLayout",
    "excerpt": "Part 2 of my experience as a new user of Go."
  },
  "md": "# Getting started with Go pointers\n\nThis is part 2 of my experience as a new user of Go, focusing on the quirks and gotchas of pointers. For installation, testing, and packages, see [Getting started with Go](getting-started-with-go).\n\nIf you'd like to follow along, and try out out the code in this article, all you need is the Go [playground](https://play.golang.org/p/-UiUJFrloVT) to run the examples.\n\n## Pointers\n\nThe [shortscale](https://github.com/jldec/shortscale-go/blob/main/shortscale.go) package which I covered last time, uses a string [Builder](https://pkg.go.dev/strings#Builder). Here is the example from the Builder docs.\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\nfunc main() {\n\tvar b strings.Builder\n\tfor i := 3; i >= 1; i-- {\n\t\tfmt.Fprintf(&b, \"%d...\", i)\n\t}\n\tb.WriteString(\"ignition\")\n\tfmt.Println(b.String())\n}\n```\n\nNotice that `var b` is an instance of the Builder. When you run the code, it will output: _3...2...1...ignition_.\n\n## Pointer receiver methods and interfaces\n\nThe first argument to fmt.Fprintf is `&b`, a [pointer](https://tour.golang.org/moretypes/1) to b. This is necessary, because [fmt.Fprintf](https://pkg.go.dev/fmt#Fprintf) expects an [io.Writer](https://pkg.go.dev/io#Writer) interface.\n\n```go\ntype Writer interface {\n\tWrite(p []byte) (n int, err error)\n}\n```\n\nThe [Builder.Write](https://pkg.go.dev/strings#Builder.Write) method matches the io.Writer interface. Notice the pointer syntax in the method receiver after the `func` keyword.\n\n```go\nfunc (b *Builder) Write(p []byte) (int, error)\n```\n\nI was tempted to replace `Fprintf(&b, ...)` with `Fprintf(b, ...)`, to make it more consistent with the `b.WriteString()` and `b.String()` further down, but doing this causes the compiler to complain:\n\n_\"cannot use b (type strings.Builder) as type io.Writer in argument to fmt.Fprintf:\nstrings.Builder does not implement io.Writer (**Write method has pointer receiver**)\"_\n\n## Value vs. pointer function arguments\n\nWhat if, instead of depending on the Writer interface, we called our own `write()` function?\n\n```go\nfunc main() {\n\tvar b strings.Builder\n\tfor i := 3; i >= 1; i-- {\n\t\twrite(b, fmt.Sprintf(\"%d...\", i))\n\t}\n\tb.WriteString(\"ignition\")\n\tfmt.Println(b.String())\n}\n\nfunc write(b strings.Builder, s string) {\n\tb.WriteString(s)\n}\n```\n\nRunning the code above in the [example sandbox](https://pkg.go.dev/strings#Builder) outputs just the word _ignition_. \n\nThe 3 calls to `write(b)` do not modify the builder declared at the top.\n\nThis makes sense, because passing a struct to a function [copies](https://tour.golang.org/methods/4) the struct value.\n\nTo fix this, we have to use a pointer to pass the struct by reference, and we have to invoke the function with `write(&b, ...)`. This works, but it doesn't make the code any more consistent.\n\n```go\nfunc main() {\n\tvar b strings.Builder\n\tfor i := 3; i >= 1; i-- {\n\t\twrite(&b, fmt.Sprintf(\"%d...\", i))\n\t}\n\tb.WriteString(\"ignition\")\n\tfmt.Println(b.String())\n}\n\nfunc write(b *strings.Builder, s string) {\n\tb.WriteString(s)\n}\n```\n\n## Why do the method calls work?\n\nWhy are we allowed to use `b` instead of `&b` in front of [b.WriteString](https://pkg.go.dev/strings#Builder.WriteString) and [b.String](https://pkg.go.dev/strings#Builder.String)? This is explained in [the tour](https://tour.golang.org/methods/6) as well.\n\n_\"...even though v is a value and not a pointer, the method with the pointer receiver is called automatically. That is, as a convenience, Go interprets the statement v.Scale(5) as (&v).Scale(5) since the Scale method has a pointer receiver.\"_\n\n## Start with a pointer\n\nIf all this mixing of values and pointers feels inconsistent, why not start with a pointer from the beginning?\n\nThe following code will compile just fine, but can you tell what's wrong with it?\n\n```go\nfunc main() {\n\tvar b *strings.Builder\n\tfor i := 3; i >= 1; i-- {\n\t\tfmt.Fprintf(b, \"%d...\", i)\n\t}\n\tb.WriteString(\"ignition\")\n\tfmt.Println(b.String())\n}\n```\nThe declaration above results in a nil pointer panic at run time, because b is uninitialized.\n\n> _panic: runtime error: invalid memory address or nil pointer dereference  \n[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x4991c7]_\n\n## Create the Builder with new()\n\nHere is one way to initialize a pointer so that it references a new Builder.\n\n```go\nfunc main() {\n\tb := new(strings.Builder)\n\tfor i := 3; i >= 1; i-- {\n\t\tfmt.Fprintf(b, \"%d...\", i)\n\t}\n\tb.WriteString(\"ignition\")\n\tfmt.Println(b.String())\n}\n```\n\n`new(strings.Builder)` returns a pointer to a freshly allocated Builder, which we can use for both functions and pointer receiver methods. This is the pattern which I now use in [shortscale-go](https://github.com/jldec/shortscale-go/blob/2485be23ef48660d8913b2ac884030220dc82d74/shortscale.go#L17-L24).\n\n\nAn alternative, which does the same thing, is the more explicit [struct literal](https://tour.golang.org/moretypes/5) shown below.\n\n```go\nfunc main() {\n\tb := &strings.Builder{}\n\t...\n```\n\n> There's no avoiding pointers in Go.  \n> Learn the quirks and the gotchas today.  \n> ✨ Keep learning! ✨\n\n_To leave a comment  \nplease visit [dev.to/jldec](https://dev.to/jldec/getting-started-with-go-part-2-pointers-4a47)_\n\n",
  "html": "<h1>Getting started with Go pointers</h1>\n<p>This is part 2 of my experience as a new user of Go, focusing on the quirks and gotchas of pointers. For installation, testing, and packages, see <a href=\"getting-started-with-go\">Getting started with Go</a>.</p>\n<p>If you'd like to follow along, and try out out the code in this article, all you need is the Go <a href=\"https://play.golang.org/p/-UiUJFrloVT\">playground</a> to run the examples.</p>\n<h2>Pointers</h2>\n<p>The <a href=\"https://github.com/jldec/shortscale-go/blob/main/shortscale.go\">shortscale</a> package which I covered last time, uses a string <a href=\"https://pkg.go.dev/strings#Builder\">Builder</a>. Here is the example from the Builder docs.</p>\n<pre><code class=\"language-go\">package main\n\nimport (\n\t&quot;fmt&quot;\n\t&quot;strings&quot;\n)\n\nfunc main() {\n\tvar b strings.Builder\n\tfor i := 3; i &gt;= 1; i-- {\n\t\tfmt.Fprintf(&amp;b, &quot;%d...&quot;, i)\n\t}\n\tb.WriteString(&quot;ignition&quot;)\n\tfmt.Println(b.String())\n}\n</code></pre>\n<p>Notice that <code>var b</code> is an instance of the Builder. When you run the code, it will output: <em>3...2...1...ignition</em>.</p>\n<h2>Pointer receiver methods and interfaces</h2>\n<p>The first argument to fmt.Fprintf is <code>&amp;b</code>, a <a href=\"https://tour.golang.org/moretypes/1\">pointer</a> to b. This is necessary, because <a href=\"https://pkg.go.dev/fmt#Fprintf\">fmt.Fprintf</a> expects an <a href=\"https://pkg.go.dev/io#Writer\">io.Writer</a> interface.</p>\n<pre><code class=\"language-go\">type Writer interface {\n\tWrite(p []byte) (n int, err error)\n}\n</code></pre>\n<p>The <a href=\"https://pkg.go.dev/strings#Builder.Write\">Builder.Write</a> method matches the io.Writer interface. Notice the pointer syntax in the method receiver after the <code>func</code> keyword.</p>\n<pre><code class=\"language-go\">func (b *Builder) Write(p []byte) (int, error)\n</code></pre>\n<p>I was tempted to replace <code>Fprintf(&amp;b, ...)</code> with <code>Fprintf(b, ...)</code>, to make it more consistent with the <code>b.WriteString()</code> and <code>b.String()</code> further down, but doing this causes the compiler to complain:</p>\n<p><em>&quot;cannot use b (type strings.Builder) as type io.Writer in argument to fmt.Fprintf:\nstrings.Builder does not implement io.Writer (<strong>Write method has pointer receiver</strong>)&quot;</em></p>\n<h2>Value vs. pointer function arguments</h2>\n<p>What if, instead of depending on the Writer interface, we called our own <code>write()</code> function?</p>\n<pre><code class=\"language-go\">func main() {\n\tvar b strings.Builder\n\tfor i := 3; i &gt;= 1; i-- {\n\t\twrite(b, fmt.Sprintf(&quot;%d...&quot;, i))\n\t}\n\tb.WriteString(&quot;ignition&quot;)\n\tfmt.Println(b.String())\n}\n\nfunc write(b strings.Builder, s string) {\n\tb.WriteString(s)\n}\n</code></pre>\n<p>Running the code above in the <a href=\"https://pkg.go.dev/strings#Builder\">example sandbox</a> outputs just the word <em>ignition</em>.</p>\n<p>The 3 calls to <code>write(b)</code> do not modify the builder declared at the top.</p>\n<p>This makes sense, because passing a struct to a function <a href=\"https://tour.golang.org/methods/4\">copies</a> the struct value.</p>\n<p>To fix this, we have to use a pointer to pass the struct by reference, and we have to invoke the function with <code>write(&amp;b, ...)</code>. This works, but it doesn't make the code any more consistent.</p>\n<pre><code class=\"language-go\">func main() {\n\tvar b strings.Builder\n\tfor i := 3; i &gt;= 1; i-- {\n\t\twrite(&amp;b, fmt.Sprintf(&quot;%d...&quot;, i))\n\t}\n\tb.WriteString(&quot;ignition&quot;)\n\tfmt.Println(b.String())\n}\n\nfunc write(b *strings.Builder, s string) {\n\tb.WriteString(s)\n}\n</code></pre>\n<h2>Why do the method calls work?</h2>\n<p>Why are we allowed to use <code>b</code> instead of <code>&amp;b</code> in front of <a href=\"https://pkg.go.dev/strings#Builder.WriteString\">b.WriteString</a> and <a href=\"https://pkg.go.dev/strings#Builder.String\">b.String</a>? This is explained in <a href=\"https://tour.golang.org/methods/6\">the tour</a> as well.</p>\n<p><em>&quot;...even though v is a value and not a pointer, the method with the pointer receiver is called automatically. That is, as a convenience, Go interprets the statement v.Scale(5) as (&amp;v).Scale(5) since the Scale method has a pointer receiver.&quot;</em></p>\n<h2>Start with a pointer</h2>\n<p>If all this mixing of values and pointers feels inconsistent, why not start with a pointer from the beginning?</p>\n<p>The following code will compile just fine, but can you tell what's wrong with it?</p>\n<pre><code class=\"language-go\">func main() {\n\tvar b *strings.Builder\n\tfor i := 3; i &gt;= 1; i-- {\n\t\tfmt.Fprintf(b, &quot;%d...&quot;, i)\n\t}\n\tb.WriteString(&quot;ignition&quot;)\n\tfmt.Println(b.String())\n}\n</code></pre>\n<p>The declaration above results in a nil pointer panic at run time, because b is uninitialized.</p>\n<blockquote>\n<p><em>panic: runtime error: invalid memory address or nil pointer dereference<br>\n[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x4991c7]</em></p>\n</blockquote>\n<h2>Create the Builder with new()</h2>\n<p>Here is one way to initialize a pointer so that it references a new Builder.</p>\n<pre><code class=\"language-go\">func main() {\n\tb := new(strings.Builder)\n\tfor i := 3; i &gt;= 1; i-- {\n\t\tfmt.Fprintf(b, &quot;%d...&quot;, i)\n\t}\n\tb.WriteString(&quot;ignition&quot;)\n\tfmt.Println(b.String())\n}\n</code></pre>\n<p><code>new(strings.Builder)</code> returns a pointer to a freshly allocated Builder, which we can use for both functions and pointer receiver methods. This is the pattern which I now use in <a href=\"https://github.com/jldec/shortscale-go/blob/2485be23ef48660d8913b2ac884030220dc82d74/shortscale.go#L17-L24\">shortscale-go</a>.</p>\n<p>An alternative, which does the same thing, is the more explicit <a href=\"https://tour.golang.org/moretypes/5\">struct literal</a> shown below.</p>\n<pre><code class=\"language-go\">func main() {\n\tb := &amp;strings.Builder{}\n\t...\n</code></pre>\n<blockquote>\n<p>There's no avoiding pointers in Go.<br>\nLearn the quirks and the gotchas today.<br>\n✨ Keep learning! ✨</p>\n</blockquote>\n<p><em>To leave a comment<br>\nplease visit <a href=\"https://dev.to/jldec/getting-started-with-go-part-2-pointers-4a47\">dev.to/jldec</a></em></p>\n"
}