Указатель — это переменная, хранящая адрес памяти другой переменной. В Go указатели типизированы и безопасны: нет арифметики указателей (кроме пакета unsafe).
Основы
package main
import "fmt"
func main() {
// & — address-of operator (get pointer)
x := 42
p := &x // p is *int, holds address of x
fmt.Println(x) // 42 — value
fmt.Println(p) // 0xc00001a0b8 — address
fmt.Println(*p) // 42 — dereference (value at address)
// * — dereference operator (get value at address)
*p = 100
fmt.Println(x) // 100 — x is modified through the pointer
// Type of a pointer
fmt.Printf("Type of p: %T\n", p) // *int
// Pointer to pointer
pp := &p
fmt.Printf("Type of pp: %T\n", pp) // **int
fmt.Println(**pp) // 100
// new() — allocates zero-value and returns pointer
q := new(int) // *int, points to 0
fmt.Println(*q) // 0
*q = 77
fmt.Println(*q) // 77
}
Операторы указателей
| Оператор | Название | Описание | Пример |
|---|---|---|---|
&x |
Address-of | Получить адрес переменной | p := &x |
*p |
Dereference | Получить значение по адресу | val := *p |
*T |
Pointer type | Тип «указатель на T» | var p *int |
new(T) |
Allocation | Выделить память, вернуть *T |
p := new(int) |
nil-указатели
package main
import "fmt"
func main() {
// Zero value of any pointer is nil
var p *int
fmt.Println(p) // <nil>
fmt.Println(p == nil) // true
// Dereferencing nil pointer causes PANIC
// fmt.Println(*p) // panic: runtime error: invalid memory address
// Always check for nil before dereferencing
if p != nil {
fmt.Println(*p)
}
// Safe access pattern
value := safeDeref(p, 42) // Returns default if nil
fmt.Println(value) // 42
}
// Generic-style safe dereference helper
func safeDeref[T any](p *T, defaultVal T) T {
if p == nil {
return defaultVal
}
return *p
}
Nil receiver в методах
package main
import "fmt"
type List struct {
Value int
Next *List
}
// Pointer receiver can handle nil!
func (l *List) Len() int {
if l == nil {
return 0 // Gracefully handle nil
}
count := 0
for current := l; current != nil; current = current.Next {
count++
}
return count
}
func (l *List) String() string {
if l == nil {
return "[]"
}
return fmt.Sprintf("[%d -> %s]", l.Value, l.Next)
}
func main() {
var empty *List
fmt.Println(empty.Len()) // 0 (no panic!)
fmt.Println(empty.String()) // []
list := &List{1, &List{2, &List{3, nil}}}
fmt.Println(list.Len()) // 3
fmt.Println(list.String()) // [1 -> [2 -> [3 -> []]]]
}
Value vs Pointer семантика
Одна из ключевых концепций Go — понимание, когда передавать значение, а когда указатель.
Передача по значению (Value semantics)
package main
import "fmt"
type Point struct {
X, Y int
}
// Value parameter — receives a COPY
func doubleValue(p Point) Point {
p.X *= 2
p.Y *= 2
return p // Must return the modified copy
}
func main() {
original := Point{10, 20}
doubled := doubleValue(original)
fmt.Println(original) // {10 20} — NOT modified
fmt.Println(doubled) // {20 40} — new copy
}
Передача по указателю (Pointer semantics)
package main
import "fmt"
type Point struct {
X, Y int
}
// Pointer parameter — receives address, modifies original
func doublePointer(p *Point) {
p.X *= 2
p.Y *= 2
// No need to return — modifies in place
}
func main() {
original := Point{10, 20}
doublePointer(&original)
fmt.Println(original) // {20 40} — modified in place
}
Когда использовать указатели
| Критерий | Value | Pointer |
|---|---|---|
| Размер структуры | Маленькая (<= 64 байт) | Большая (> 64 байт) |
| Мутация | Не нужна | Нужна |
| nil-семантика | Не нужна | Нужна (optional fields) |
| Интерфейсы | T и *T удовлетворяют | Только *T удовлетворяет |
| Слайс элементов | []T (data locality) |
[]*T (если элементы большие) |
| Конкурентный доступ | Безопасно (копии) | Нужна синхронизация |
| Встроенные типы | int, string, bool |
— (передавать по значению) |
// Value semantics — for small, immutable data
type Color struct {
R, G, B uint8 // 3 bytes — definitely pass by value
}
// Pointer semantics — for large or mutable data
type DatabaseConfig struct {
Host string
Port int
Username string
Password string
Database string
SSLMode string
Options map[string]string
// Large struct — use pointer
}
// Pointer semantics — for shared state
type Cache struct {
mu sync.RWMutex
items map[string]any
// Must be shared — use pointer
}
Stack vs Heap (Escape Analysis)
Компилятор Go автоматически решает, где разместить переменную — на стеке или в куче. Это называется escape analysis.
package main
import "fmt"
type User struct {
Name string
Age int
}
// User stays on the stack — does not escape
func createLocal() {
u := User{Name: "Alice", Age: 30}
fmt.Println(u.Name) // u is used only within this function
}
// User escapes to the heap — returned pointer outlives the function
func createAndReturn() *User {
u := User{Name: "Bob", Age: 25}
return &u // u escapes! Allocated on heap
}
// User escapes to the heap — assigned to interface
func printUser() {
u := User{Name: "Charlie", Age: 35}
var i interface{} = u // u escapes! Interface stores on heap
fmt.Println(i)
}
func main() {
createLocal()
u := createAndReturn()
fmt.Println(u.Name)
printUser()
}
Как проверить escape analysis
# Show escape analysis decisions
go build -gcflags="-m" ./...
# More verbose
go build -gcflags="-m -m" ./...
# Output example:
# ./main.go:10:2: u does not escape
# ./main.go:16:9: &u escapes to heap
Что вызывает escape
| Причина escape | Пример |
|---|---|
| Возврат указателя | return &x |
| Запись в интерфейс | var i interface{} = x |
| Запись в слайс | s = append(s, &x) |
| Замыкание захватывает | go func() { _ = x }() |
| Слишком большой объект | Объект > определённого размера |
| Компилятор не может доказать безопасность | Сложные flow |
Совет: Escape на heap — не катастрофа. Не оптимизируйте преждевременно. Профилируйте реальную нагрузку через
pprof, прежде чем беспокоиться о heap allocations.
Отсутствие арифметики указателей
Go запрещает арифметику указателей — это ключевое отличие от C/C++:
func main() {
arr := [5]int{10, 20, 30, 40, 50}
p := &arr[0]
// C/C++: p++ to move to next element — ILLEGAL in Go
// p++ // COMPILE ERROR
// p += 1 // COMPILE ERROR
// *(p+1) // COMPILE ERROR
// In Go, use indexing instead
for i := range arr {
fmt.Println(arr[i])
}
// unsafe.Pointer exists but should be avoided
// Only for interop with C (cgo) or low-level runtime code
_ = p
}
Это делает Go безопаснее — нет buffer overflows, use-after-free и других проблем, типичных для C.
Практические паттерны
Опциональные поля с указателями
package main
import (
"encoding/json"
"fmt"
)
type UpdateUserRequest struct {
Name *string `json:"name,omitempty"` // nil = don't update
Email *string `json:"email,omitempty"` // nil = don't update
Age *int `json:"age,omitempty"` // nil = don't update
}
// Helper function to create pointer to value
func ptr[T any](v T) *T {
return &v
}
func main() {
// Update only name
req := UpdateUserRequest{
Name: ptr("Alice"),
// Email and Age are nil — not updated
}
data, _ := json.Marshal(req)
fmt.Println(string(data)) // {"name":"Alice"}
// Update name and age
req2 := UpdateUserRequest{
Name: ptr("Bob"),
Age: ptr(30),
}
data, _ = json.Marshal(req2)
fmt.Println(string(data)) // {"name":"Bob","age":30}
}
Указатели для передачи больших структур
package main
import "fmt"
type LargeData struct {
Matrix [1000][1000]float64 // 8 MB!
}
// BAD: copies 8 MB on each call
func processValue(data LargeData) float64 {
return data.Matrix[0][0]
}
// GOOD: passes only 8 bytes (pointer)
func processPointer(data *LargeData) float64 {
return data.Matrix[0][0]
}
// GOOD with const correctness note:
// Go doesn't have const pointers, but document intent
func processReadOnly(data *LargeData) float64 {
// This function should NOT modify data
return data.Matrix[0][0]
}
Паттерн: swap
func swap[T any](a, b *T) {
*a, *b = *b, *a
}
func main() {
x, y := 10, 20
swap(&x, &y)
fmt.Println(x, y) // 20, 10
s1, s2 := "hello", "world"
swap(&s1, &s2)
fmt.Println(s1, s2) // world, hello
}
Ссылочные типы Go (уже являются указателями)
Некоторые типы в Go уже содержат указатели внутри — передавать их по указателю не нужно:
| Тип | Содержит указатель? | Передавать *T? |
|---|---|---|
slice ([]T) |
Да (ptr + len + cap) | Нет (если не меняем len/cap) |
map |
Да (pointer to hash table) | Нет |
channel |
Да (pointer to hchan) | Нет |
function |
Да (pointer) | Нет |
interface |
Да (type + data pointers) | Нет |
string |
Да (ptr + len) | Нет |
struct |
Нет | Зависит от размера и мутации |
array |
Нет | Да (если большой) |
int, bool, etc. |
Нет | Нет (маленькие, копирование дёшево) |