I have this fragment of Go code where I'm trying to change the values of a regular int and an int in a struct using two functions f
and f2
respectively. I don't understand why I need to do *i
to change the value of the int but I don't need to do that when I change the value of X in the struct.
type Point struct {
X int
}
func t(i *int) {
*i = 20
}
func t2(p *Point) {
p.X = 200
}
func main() {
g := 30
t(&g)
fmt.Println(g)
p := Point{3}
t2(&p)
fmt.Println(p)
}
The easiest way to think about both functions is that in the t2
function, you're changing a field of a struct, using a pointer to the underlying struct. In the t
function, you're changing the entire underlying object (the int).
In reality, the fact that you can write p.X
is actually just a nicety. In languages like C, you could only use p.X
if you were operating on a non-pointer variable. For pointers, you had to either use p->X
denoting you were accessing a field using indirection, or indeed dereference the pointer ((*p).X
).
Internally, go still does the same thing, it just allows you to omit the explicit dereferencing, and it eliminates the need for an indirection operator.
Both functions, however, are not equivalent. Point
is a struct, with one or more fields. The other type is *int
, an int is a single scalar value. To make t2
equivalent (and reassign the entire underlying object), you'll have to change the code to be identical to what you have to do in case of *int
:
func t2(p *Point) {
*p = Point{
X: 200,
Y: p.Y,
}
}
As per comment below: the TL;DR version is that you don't have to explicitly dereference a pointer to a struct type if you access one of its fields. You had to do that in C/C++, but the go compiler takes care of that for you. It works out you're using a variable that is a pointer type and compiles p.X
in the same way that a C compiler would compile p->X
. Therefore, you don't need to dereference p
explicitly.
You would still have to write *p.X
if you declared Point
as:
type Point struct {
X *int
}
Because the expression p.X
evaluates to an operand of type *int
, which needs to be treated accordingly.
Because i
is a pointer of type *int
, and you want to modify the pointed object, so you have to write *i
.
The same syntax is valid for structs too, e.g. you can write (*p).X
, but this is a frequent operation, so the spec allows to use p.X
which will mean (*p).X
, there is no ambiguity, and is a handy shortcut.
It's in Spec: 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
.