Pointers¶
Pointers are mainly used to access variables directly inside functions.
Similar to C, but not as complicated as pointers in C.
Starting with this example; reading user input to get their name.
fmt.Println("What is your name? ${white}")
var name string
fmt.Scanln(&name) // This is the memory address (a pointer)
// This is passing-by-reference.
fmt.Scanln
reads from standard input (user input on the CLI).It stops reading at newline (
<CR>
) or end of file.
Since fmt.Scanln
needs to modify the value of the name
variable, the only way
to do this is to pass a Pointer to the function - its memory address (&name
).
&name
passes the memory address of thename
variable (a pointer) tofmt.Scanln
.fmt.Scanln
dereferences the pointer (accesses the value stored at the memory address) and modifies the actualname
variable.
Why a pointer is needed here:
* fmt.Scanln
needs to assign the user input to the name
variab.e
Without passing its address, fmt.Scanln
would only work with a copy* of name
and wouldn't be able to modify the original variable.
So, if you need to pass a variable to a function in order to modify its value, use a pointer.
Example: Using a pointer to modify a variable directly¶
package main
import ("fmt")
func ChangeAge(age *int) newage int {
*age = newage
}
func main() {
var age int = 30
fmt.Println("Your age:", age)
ChangeAge(&age, 33)
fmt.Printf("New age: %v\n", age)
}
If you called ChangeAge
without referencing &age
, it would only pass the value
of age
(30
).
ChangeAge(age, 33)
// is the same as:
ChangeAge(30, 33)
Pointer Receivers in Functions (Methods)¶
When creating methods for structs, you can use Pointer Receivers to modify the actual struct itself.
Ex: Task list project I'm working on:
package tasks
// this is in ./internal/tasks/tasks.go
type Task struct {
Title string,
Description string,
Completed bool
}
type TaskList struct {
Tasks []Task
}
// Create some methods for the new objects
// Use a pointer receiver to access the variable itself
func (tl *TaskList) AddTask(title string, desc string) {
newTask := Task{
Title: title,
Description: desc,
Completed: false,
}
tl.Tasks = append(tl.Tasks, newTask)
}
This uses func (tl *TaskList)
, which is called a receiver.
A receiver is used to associate a method with a type.
The receiver defines it as a method for the type TaskList
.
It uses the *
in front of TaskList
so that it can access the variable itself,
making this a pointer receiver.
When using a pointer receiver, any changes made to tl
(the TaskList
object)
inside the method affects the original object instead of a copy.
This method can be called directly without explicitly passing a pointer.
E.g.,
package main
import (
"fmt"
t "github.com/terminal-todo/internal/tasks"
)
func main() {
var tl t.TaskList
tl.AddTask("Task1", "This is the task description")
fmt.Printf("Current task: %v", tl.Tasks[0])
}