A task has a title, a completion flag, and perhaps some tags. Passing those as unrelated variables makes it easy to mix them up. A struct groups related fields into one value. A method is a function attached to a type. A pointer lets code refer to an existing value when copying it would not express the intended behavior.
Go uses garbage collection to reclaim memory that is no longer reachable. You do not manually free ordinary Go objects. That convenience does not remove the need to understand sharing: two parts of your program can still change the same object, and unsynchronized concurrent changes can be incorrect.
Addresses, dereferencing, and nil
For a variable task, &task produces its address, a pointer to that value. If p points to a Task, its type is *Task. *p accesses the value that the pointer refers to; this operation is called dereferencing. Go also lets you write p.Title for a struct field instead of (*p).Title.
The zero value of a pointer is nil, meaning it does not point to a value. Dereferencing a nil pointer panics. A pointer does not perform automatic initialization. Use an existing variable's address or a struct literal such as &Task{Title: "Read"} when you need an object. The pointer tour demonstrates the basic operators.
Run a model with methods
Create a fresh module and save this complete program as main.go:
package main
import "fmt"
type Task struct {
Title string
Done bool
Tags []string
}
func (t Task) Label() string {
return fmt.Sprintf("%s (done=%t)", t.Title, t.Done)
}
func (t *Task) Complete() {
t.Done = true
}
func main() {
original := Task{
Title: "Read Go",
Tags: []string{"learn"},
}
duplicate := original
duplicate.Title = "Practice Go"
duplicate.Tags[0] = "shared"
duplicate.Complete()
fmt.Println(original.Label(), original.Tags)
fmt.Println(duplicate.Label(), duplicate.Tags)
pointer := &original
pointer.Complete()
fmt.Println("original completed:", original.Done)
var absent *Task
fmt.Println("absent:", absent == nil)
}
Run go run .. Expected output:
Read Go (done=false) [shared]
Practice Go (done=true) [shared]
original completed: true
absent: true
duplicate := original copies the struct's fields. Changing the copied title or boolean does not change the original fields. However, copying Tags copies a slice value that still refers to shared element storage. That is why both tasks show shared. A struct copy is not automatically a deep copy.
The struct tour covers field access and literals. Prefer named fields in literals so adding or reordering a field does not make call sites confusing.
Choose a receiver by meaning
The parameter before the method name is its receiver. Label uses a value receiver and reads a copy. Complete uses a pointer receiver and changes the caller's task. Calling duplicate.Complete() is convenient syntax: because duplicate is an addressable variable, Go can take its address for this call. It does not change the declaration of the method.
Use pointer receivers for methods that must mutate the receiver, and for types that must not be copied, such as structs containing an active mutex. Value receivers fit small values whose behavior does not require mutation. Do not assume a pointer receiver is always faster; performance depends on the program and should be measured when it matters. See the pointer receiver explanation.
Even a value receiver could modify shared data reached through a slice or pointer field. Receiver choice describes how the receiver value is passed; it is not an immutability guarantee.
Model boundaries and composition
Exported fields are part of your package's public API. For stronger invariants, keep fields lowercase and expose methods or constructors that validate changes. A constructor is an ordinary function, conventionally named something like NewTask; Go does not require one for every type. Useful zero values can reduce unnecessary setup.
Go builds larger models through composition: one struct can contain another struct as a field. Embedding can promote methods for convenient access, but it is not a class inheritance hierarchy. Start with explicit named fields when the relationship is easier to understand that way.
Exercise: make tags independent
Change the example so updating duplicate.Tags[0] leaves the original tag as learn. Keep independent titles and completion flags. Add a pointer variable that refers to duplicate, and predict what happens when it calls Complete.
Solution guidance
Immediately after copying the struct, allocate a new tag slice with make, then copy the original tags into it and assign it to duplicate.Tags. Now the two tasks have separate element storage. A pointer to duplicate still changes that same duplicate; taking an address does not make another task. Verify the original's tag remains learn before its own completion call.
Previous: Collections and Strings. Next: Interfaces.

