Help With This Use of Pointers

I’ve never programmed in a language with pointers before. I wrote this code and I’m not actually sure why it works:

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
)

type Gist struct {
	AccessToken string
	Files       []string
}

type Conf struct {
	Gist Gist
}

// Attempts to read the .sinkerrc.json file in the user's
// home directory
func ReadSinkerRc() ([]byte, error) {
	homdir, err := os.UserHomeDir()
	if err != nil {
		return nil, err
	}
	data, err := ioutil.ReadFile(path.Join(homdir, ".sinkerrc.json"))
	if err != nil {
		return nil, err
	}
	return data, nil
}

// Parses the json from the config.
func ParseJsonConfg(data []byte) (Conf, error) {
	var conf Conf
	err := json.Unmarshal(data, &conf)
	return conf, err
}

// Gets the configuration data, taking care of reading
// and parsing the json from the config file
func Get() (*Conf, error) {
	data, err := ReadSinkerRc()
	if err != nil {
		return nil, err
	}
	conf, err := ParseJsonConfg(data)
	if err != nil {
		return nil, err
	}
	return &conf, nil
}

func main() {
	config, err := Get()
	if err != nil {
		log.Fatal(err.Error())
	}
	fmt.Println(config.Gist.AccessToken)

}

Get returns a pointer to a Conf and an error because otherwise we can’t express a nil Conf, but we can express a nil pointer because the default 0 value of a pointer is nil. And there is no way to have a nil value of a struct, right? If so, why not?

What I don’t understand is that if Get returns a memory address to a Conf and an error, why in main I can access its properties of Conf without getting its underlying value? Why don’t I need to do *conf to get the underlying value?

Because a struct is a value in memory and a pointer is an address of memory. The value of an uninitialized pointer is nil . Each element of an uninitialized struct value is set to the zero value for its type. Of course, you can have a pointer to a struct and the pointer can have a nil value.


Because the Go compiler, if necessary, will dereference config. for you: config. is shorthand for (*config).

The Go Programming Language Specification

Selectors

For a primary expression x that is not a package name, the selector expression

x.f

denotes the field or method f of the value x (or sometimes *x; see below). The identifier f is called the (field or method) selector; it must not be the blank identifier. The type of the selector expression is the type of f. If x is a package name, see the section on qualified identifiers.

A selector f may denote a field or method f of a type T, or it may refer to a field or method f of a nested embedded field of T. The number of embedded fields traversed to reach f is called its depth in T. The depth of a field or method f declared in T is zero. The depth of a field or method f declared in an embedded field A in T is the depth of f in A plus one.

The following rules apply to selectors:

As an exception, if the type of x is a defined pointer type and (*x).f is a valid selector expression denoting a field (but not a method), x.f is shorthand for (*x).f.

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
)

type Gist struct {
	AccessToken string
	Files       []string
}

type Conf struct {
	Gist Gist
}

// Attempts to read the .sinkerrc.json file in the user's
// home directory
func ReadSinkerRc() ([]byte, error) {
	homdir, err := os.UserHomeDir()
	if err != nil {
		return nil, err
	}
	data, err := ioutil.ReadFile(path.Join(homdir, ".sinkerrc.json"))
	if err != nil {
		return nil, err
	}
	return data, nil
}

// Parses the json from the config.
func ParseJsonConfg(data []byte) (Conf, error) {
	var conf Conf
	err := json.Unmarshal(data, &conf)
	return conf, err
}

// Gets the configuration data, taking care of reading
// and parsing the json from the config file
func Get() (*Conf, error) {
	data, err := ReadSinkerRc()
	if err != nil {
		return nil, err
	}
	conf, err := ParseJsonConfg(data)
	if err != nil {
		return nil, err
	}
	return &conf, nil
}

func main() {
	config, err := Get()
	if err != nil {
		log.Fatal(err.Error())
	}
	fmt.Println(config.Gist.AccessToken)

	fmt.Printf("%T %v\n", config.Gist.AccessToken, config.Gist.AccessToken)
	fmt.Printf("%T %v\n", (*config).Gist.AccessToken, (*config).Gist.AccessToken)
}

.

$ go run sinkerrc.go
accesstoken
string accesstoken
string accesstoken
$

I saw this answered by, I believe, Rus Cox somewhere, but I’m unable to find it now. The Go language designers felt that it was fine to use . both for accessing the field of a struct value or a struct pointer because it was unambiguous. I feel like it’s kinda like this:

var a, b float32

a = 1.2
b = 3.4

// Should we compile the * operation as a CPU integer multiply or as a floating
// point multiply?  It's obvious!  a and b are floating point numbers, so use
// the floating point multiply!
c := a * b

var d struct{ e float32 }

// Is the . for a pointer or for a value?  d is a struct value, so obviously the
// latter!
d.e = c

var f *struct{ g float32 } = new(struct{ float32 })

// Again, the type makes this obvious.
f.g = c

You could write fmt.Println((*config).Gist.AccessToken), but it’s unnecessary because what else could you have reasonably meant by the .?

Thank you so much. I understand now. That really confused me. I appreciate your time.

Another question…the use of pointers seems to violate a lot of the practices I’ve used for years in languages like JavaScript, Python etc. where I try to avoid implicit changes outside of function scope.

It seems mutating variables from outside a function would be dangerous in the sense that it might be hard to track down who is changing what. That said, I think I understand the point of pointers, which is exactly to allow this in order to avoid copying a data structure to a function’s scope. In the case of large data structures this makes sense in a language more geared to performance. Are my observations on track?

Is there another specific use-case for using pointers other than for performance reasons?

Performance isn’t really a specific use-case for using pointers. The primary purpose for using pointers is so that you have shared mutability of a value, so that everything with a reference to that thing observes changes. You should use a pointer when you want that mutability, and a non-pointer value if not.

Depending on the behaviors of your program, and what the escape analyzer decides, using pointers could hurt the performance of your program by allocating items on the heap instead of the stack thus incurring more stop-the-world garbage collection.

Great explanation. So an example might be a config change where you might want other code to pick up on without restarting the app?

As an example, here in the golang bindings for the github API, a Gist struct has a embedded Files struct, which in turn has a Filename field, which instead of a string is a pointer. Can you help me understand why it is useful to mutate that string outside of function scope for reasons other than performance?

For this particular example, based on the json tags in the struct fields, it looks like the field is mutable so that it can be deserialized into by json.Unmarshal (or some other compatible implementation). Without reading their code, I don’t see the reason why they’re using *string instead of string. That seems to imply that the fields could be null in the JSON and that there’s a meaningful difference in the Go code between a nil *string and a "" string.

I’m not sure what you mean about it being useful to mutate that string outside of function scope.

1 Like

What I’m asking is that in general I have always coded without explicit pointers and have always returned new values from the function scope. One of the answers to “why pointers” in this post was that it is not really for performance per se, but rather so we can observe changes of state system-wide and react. I am specifically wondering when is a good and common use case that illustrates this assertion.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.