Pointers were probably one of the concepts I was most curious about when I started learning Go.
Coming from Ruby, I don't normally think about memory addresses or explicitly passing references around.
In Go, pointers are much more visible.
At first, they looked like one of those concepts I remembered from C++ and hoped I wouldn't have to think about again.
But once I understood what they actually represent, they became much less scary.
So What Is a Pointer?
A pointer is a value that stores the memory address of another value.
For example:
age := 30
pointer := &age
The & operator gives us the address of age.
So instead of:
age
which represents the value:
30
we have:
pointer
which represents the address where 30 is stored.
Getting the Value Back
If we have a pointer, we can use * to get the value stored at that address.
age := 30
pointer := &age
fmt.Println(pointer)
fmt.Println(*pointer)
The first Println gives us the memory address.
The second gives us:
30
because *pointer means:
Go to the address stored in
pointerand give me the value there.
This is called dereferencing.
So we have two operators to remember:
& → get the address
* → get the value at that address
Changing a Value Through a Pointer
This is where pointers become really useful.
age := 30
pointer := &age
*pointer = 31
fmt.Println(age)
The result is:
31
We didn't directly change age.
We changed the value at the address stored in pointer.
Since pointer points to age, the original value changes.
Pointers with Functions
This becomes especially useful when passing values to functions.
Consider this function:
func changeAge(age int) {
age = 31
}
If we do:
age := 30
changeAge(age)
fmt.Println(age)
the value is still:
30
That's because the function receives a copy of the value.
If we want the function to modify the original value, we can pass a pointer:
func changeAge(age *int) {
*age = 31
}
Then:
age := 30
changeAge(&age)
fmt.Println(age)
Now the result is:
31
We pass the address:
&age
and inside the function we dereference it:
*age
This Is Where Ruby Feels Very Different
In Ruby, I don't normally have to think about passing a memory address.
For example:
def change_name(user)
user.name = "Alice"
end
If user is an object, the method can modify that object.
Ruby handles the object references for me.
Go makes the relationship much more explicit.
I have to decide whether I'm passing the value itself:
func changeAge(age int)
or a pointer to the value:
func changeAge(age *int)
That distinction matters.
Pointers and Structs
Pointers become especially common when working with structs.
Let's use the Person struct from the previous article:
type Person struct {
Name string
Age int
}
We can create a pointer to a Person:
person := &Person{
Name: "Alice",
Age: 30,
}
Now person is a pointer to a Person.
We can still access its fields directly:
fmt.Println(person.Name)
Go automatically dereferences the pointer when accessing the field.
So:
person.Name
is effectively shorthand for:
(*person).Name
This is a nice convenience because we don't have to write * every time we access a field.
Pointer Receivers
This also connects to something we saw in Part 3: methods.
Remember that Go methods can have a receiver:
func (p Person) SayHello() {
fmt.Println("Hello", p.Name)
}
This is a value receiver.
The method receives a copy of the Person.
If we want the method to modify the original Person, we can use a pointer receiver:
func (p *Person) Rename(name string) {
p.Name = name
}
Now:
person := Person{
Name: "Alice",
Age: 30,
}
person.Rename("Bob")
fmt.Println(person.Name)
prints:
Bob
The pointer receiver allows the method to modify the original value.
Value Receiver vs Pointer Receiver
This distinction is important:
func (p Person) Rename(name string)
means the method receives a copy.
While:
func (p *Person) Rename(name string)
means the method receives a pointer to the original value.
Coming from Ruby, this is one of those things I don't normally have to think about.
In Ruby, I can simply mutate the object:
def rename(person)
person.name = "Bob"
end
Go makes the decision more explicit.
Go vs Ruby
| Concept | Go | Ruby |
|---|---|---|
| Reference to a value | Pointer | Object reference handled by Ruby |
| Get address | &value |
No equivalent syntax |
| Dereference | *pointer |
No explicit dereference |
| Modify through reference | *pointer = value |
Mutate the object directly |
| Pointer receiver | func (p *Person) |
No equivalent |
| Value receiver | func (p Person) |
No direct equivalent |
| Memory address | Explicitly accessible through pointers | Normally hidden |
What Surprised Me
The syntax wasn't actually the hardest part.
It was changing the way I think about values.
In Ruby, I can mostly think:
"I have an object."
In Go, I sometimes have to ask:
"Do I have the value, or do I have a pointer to the value?"
That distinction affects how data is passed to functions and how methods can modify it.
Once I started thinking about a pointer as simply:
"A value that tells me where another value lives"
the concept became much easier.
The Main Idea
I initially thought pointers were going to be one of the most complicated parts of learning Go.
They're definitely more visible than they are in Ruby, but the basic idea is pretty simple:
& → give me the address
* → give me the value at that address
And when working with structs:
person := &Person{}
means:
personpoints to aPerson.
While:
func (p *Person)
means:
this method works with a pointer to the original
Person.
As a Ruby developer, I probably won't need to think about memory addresses every day.
But understanding pointers gives me a better picture of what's actually happening when Go passes values around.
And that's becoming one of my favorite parts of learning Go:
Ruby lets me stay comfortably high-level.
Go occasionally makes me look under the hood.
Top comments (0)