Quick Syntax
count := 3
ptr := &count
*ptr = 4
fmt.Println(count) // 4Go pointer는 값의 주소를 넘겨서 같은 값을 수정하거나 큰 값을 복사하지 않기 위한 표면입니다.
문법
&는 주소를 얻는다
count := 3
ptr := &countptr의 타입은 *int입니다. int 값을 직접 담은 것이 아니라 int 값이 있는 주소를 담습니다.
*는 pointer가 가리키는 값을 읽거나 쓴다
fmt.Println(*ptr)
*ptr = 10*ptr은 pointer가 가리키는 실제 값입니다. nil pointer를 역참조하면 panic이 납니다.
var ptr *int
// fmt.Println(*ptr) // panic함수 인자는 기본적으로 값으로 전달된다
func Rename(user User) {
user.Name = "new"
}이 함수는 User 값을 복사해서 받습니다. 호출자 쪽 값을 바꾸려면 pointer를 받아야 합니다.
func Rename(user *User) {
user.Name = "new"
}slice, map, channel은 값으로 전달해도 내부 데이터나 런타임 구조를 공유합니다. "값으로 전달된다"는 말이 항상 깊은 복사를 뜻하지는 않습니다.
pointer 자체도 값으로 복사된다
func Rebind(ptr *int) {
other := 99
ptr = &other
}
count := 3
Rebind(&count)
fmt.Println(count) // 3함수는 pointer 값도 복사해서 받습니다. *ptr = 99는 같은 대상 값을 바꾸지만, ptr = &other는 함수 안의 pointer 변수만 다른 주소로 바꿉니다. new(User)는 zero value User를 가리키는 *User를 만들고, &User{}는 초기값을 지정한 User의 주소를 얻습니다.
수신자
값을 바꾸는 method는 pointer receiver가 맞다
func (u *User) Rename(name string) {
u.Name = name
}method가 receiver 내부 상태를 바꾸면 pointer receiver를 씁니다.
type Renamer interface {
Rename(string)
}
var _ Renamer = (*User)(nil) // 가능
// var _ Renamer = User{} // pointer receiver만 있으면 불가pointer receiver method가 있으면 보통 *User만 그 method를 가진 interface를 만족합니다. 지역 변수 user.Rename("new")는 compiler가 주소를 취해 호출할 수 있지만, interface 대입과 map element처럼 주소를 바로 얻을 수 없는 값에는 같은 편의가 적용되지 않습니다.
작은 immutable 값은 value receiver도 자연스럽다
func (p Point) Distance() float64 {
return math.Hypot(p.X, p.Y)
}작고 변경하지 않는 값은 value receiver가 읽기 쉽습니다. 다만 같은 타입의 method receiver는 가능하면 한쪽으로 일관되게 맞추는 편이 좋습니다. struct를 값으로 복사해도 안의 slice, map, pointer field는 원본과 참조 대상을 공유할 수 있으므로, value receiver가 항상 완전한 불변 복사를 뜻하지는 않습니다.
선택 기준
| 상황 | 먼저 떠올릴 선택 |
|---|---|
| 값을 수정해야 함 | pointer |
| nil 가능성을 표현 | pointer |
| 큰 구조체 복사 회피 | pointer |
| 작은 값 읽기 전용 | value |
| method가 상태 변경 | pointer receiver |
| method가 계산만 수행 | value receiver 검토 |
주의할 점
pointer를 쓰면 수정 가능성과 nil 가능성이 같이 생깁니다. 단순히 "빠를 것 같다"는 이유만으로 모든 값을 pointer로 바꾸면 코드가 더 위험해질 수 있습니다. pointer는 동시 접근을 안전하게 만들지 않으므로 공유 pointer를 여러 goroutine이 바꾸면 race detector 기준으로 별도 동기화가 필요합니다. 값의 소유권, 변경 여부, nil을 의미 있게 다룰지부터 먼저 확인해야 합니다.
참고 링크
3 sources