🔥 값 또는 포인터 리시버 선택하기

361자
5분

Go 언어에서 메서드의 리시버로 값(value) 또는 포인터(pointer)를 사용하는 데에는 두 가지 이유가 있습니다.

첫 번째 이유는 메서드가 리시버가 가리키는 값을 수정할 수 있도록 하기 위함입니다. 다음 예제 코드를 살펴보겠습니다.

go
func (v *Vertex) Scale(f float64) {
    // 포인터 리시버를 사용하여 Vertex 구조체의 필드 값을 수정합니다.
    v.X = v.X * f
    v.Y = v.Y * f
}
 
go
func (v *Vertex) Scale(f float64) {
    // 포인터 리시버를 사용하여 Vertex 구조체의 필드 값을 수정합니다.
    v.X = v.X * f
    v.Y = v.Y * f
}
 

Scale 메서드는 포인터 리시버를 사용하여 Vertex 구조체의 XY 필드 값을 수정합니다. 만약 값 리시버를 사용한다면, 메서드 내에서 수정한 값은 메서드 호출이 끝난 후에 사라지게 됩니다.

두 번째 이유는 메서드 호출 시마다 값을 복사하는 것을 피하기 위함입니다. 예를 들어, 리시버가 큰 구조체인 경우에는 값 복사로 인한 오버헤드를 줄일 수 있습니다.

go
func (v *Vertex) Abs() float64 {
    // 값을 수정할 필요는 없지만, 포인터 리시버를 사용하여 값 복사를 피합니다.
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
 
go
func (v *Vertex) Abs() float64 {
    // 값을 수정할 필요는 없지만, 포인터 리시버를 사용하여 값 복사를 피합니다.
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
 

Abs 메서드는 리시버의 값을 수정할 필요가 없지만, 포인터 리시버를 사용하여 값 복사를 피하고 있습니다.

일반적으로 한 타입의 모든 메서드는 값 또는 포인터 리시버 중 하나만 사용하는 것이 좋습니다. 값과 포인터 리시버를 혼용하는 것은 혼란을 야기할 수 있기 때문입니다.

다음은 위 예제 코드의 전체 내용입니다.

go
package main
 
import (
    "fmt"
    "math"
)
 
type Vertex struct {
    X, Y float64
}
 
func (v *Vertex) Scale(f float64) {
    // 포인터 리시버를 사용하여 Vertex 구조체의 필드 값을 수정합니다.
    v.X = v.X * f
    v.Y = v.Y * f
}
 
func (v *Vertex) Abs() float64 {
    // 값을 수정할 필요는 없지만, 포인터 리시버를 사용하여 값 복사를 피합니다.
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
 
func main() {
    v := &Vertex{3, 4}
    fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs())
    // 크기 조정 전 Vertex 값과 Abs 결과를 출력합니다.
 
    v.Scale(5)
    // Scale 메서드를 호출하여 Vertex 값을 5배로 크기 조정합니다.
 
    fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs())
    // 크기 조정 후 Vertex 값과 Abs 결과를 출력합니다.
}
 
go
package main
 
import (
    "fmt"
    "math"
)
 
type Vertex struct {
    X, Y float64
}
 
func (v *Vertex) Scale(f float64) {
    // 포인터 리시버를 사용하여 Vertex 구조체의 필드 값을 수정합니다.
    v.X = v.X * f
    v.Y = v.Y * f
}
 
func (v *Vertex) Abs() float64 {
    // 값을 수정할 필요는 없지만, 포인터 리시버를 사용하여 값 복사를 피합니다.
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
 
func main() {
    v := &Vertex{3, 4}
    fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs())
    // 크기 조정 전 Vertex 값과 Abs 결과를 출력합니다.
 
    v.Scale(5)
    // Scale 메서드를 호출하여 Vertex 값을 5배로 크기 조정합니다.
 
    fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs())
    // 크기 조정 후 Vertex 값과 Abs 결과를 출력합니다.
}
 

main 함수에서는 Vertex 구조체의 포인터를 생성하고, 크기 조정 전후의 값과 Abs 메서드의 결과를 출력합니다. Scale 메서드를 호출하여 VertexXY 값을 5배로 크기 조정한 후, 다시 한 번 값과 Abs 결과를 출력하여 변경 사항을 확인할 수 있습니다.

이 예제를 통해 값 또는 포인터 리시버를 선택하는 기준과 그 차이점을 이해할 수 있습니다. 메서드가 리시버의 값을 수정해야 하는 경우나 큰 구조체의 값 복사를 피하고자 할 때는 포인터 리시버를 사용하는 것이 효과적입니다.

YouTube 영상

채널 보기
앨런 튜링이 들려주는 튜링 테스트와 보편 기계 이야기
펑터 합성 | 프로그래머를 위한 카테고리 이론
NestJS 필터 바인딩 - Method, Controller, Global Scope 비교 | NestJS 가이드
커스텀 예외 필터 만들기 | NestJS 가이드
펑터 타입 클래스 | 프로그래머를 위한 카테고리 이론
리더 펑터 - 함수도 펑터다! | 프로그래머를 위한 카테고리 이론
펑터 법칙과 등식 추론 | 프로그래머를 위한 카테고리 이론
List 펑터 - 왜 map은 for 루프보다 강력한가? | 프로그래머를 위한 카테고리 이론