Go에서 값 리시버 vs 포인터 리시버 (실용적인 설명)

발행: (2026년 2월 10일 오후 02:22 GMT+9)
5 분 소요
원문: Dev.to

Source: Dev.to

Introduction

Go에서 처음 마주치는 설계 질문 중 하나는 메서드가 값 리시버를 사용할지 포인터 리시버를 사용할지 결정하는 것입니다. 두 형태는 모두 자주 보입니다:

func (u User) Greet() {}
func (u *User) UpdateName() {}

비슷해 보이지만, 선택은 정확성, 성능, 그리고 실제 백엔드 코드에서 타입이 동작하는 방식에 영향을 미칩니다.

Value Receivers

Go에서 메서드는 타입에 붙어 있는 함수에 불과합니다.

type User struct {
    Name string
}

값 리시버로 동작을 추가하기:

func (u User) Greet() {
    fmt.Println("Hello,", u.Name)
}

여기서 u리시버라고 부르며, 메서드가 호출된 값의 복사본입니다.

user := User{Name: "Shivam"}
user.Greet()

Modifying a Value Receiver

메서드가 복사본을 받으면, 모든 수정은 그 복사본에만 영향을 미칩니다.

func (u User) ChangeName() {
    u.Name = "New Name"
}

예시

package main

import "fmt"

type User struct {
    Name string
}

func (u User) ChangeName() {
    u.Name = "New Name"
}

func main() {
    user := User{Name: "Shivam"}
    user.ChangeName()
    fmt.Println(user.Name) // Output: Shivam
}

출력은 Shivam 그대로입니다. 메서드가 복사본에서 동작했기 때문입니다.
핵심 포인트: 값 리시버 = 복사본에서 작업한다.

When to Use Value Receivers

  • 구조체가 작을 때.
  • 메서드가 리시버를 수정할 필요가 없을 때(읽기 전용 헬퍼).

전형적인 예는 Point 타입입니다:

func (p Point) Distance() float64 { /* ... */ }

Pointer Receivers

포인터 리시버는 메서드가 메모리상의 원본 구조체에 접근하도록 합니다.

func (u *User) ChangeName() {
    u.Name = "New Name"
}

예시

package main

import "fmt"

type User struct {
    Name string
}

func (u *User) ChangeName() {
    u.Name = "New Name"
}

func main() {
    user := User{Name: "Shivam"}
    user.ChangeName()
    fmt.Println(user.Name) // Output: New Name
}

변경이 지속되는 이유는 메서드가 원본 객체에서 동작했기 때문입니다.
핵심 포인트: 포인터 리시버 = 원본에서 작업한다.

Advantages of Pointer Receivers

  • 상태 수정: 필드를 업데이트해야 하는 메서드는 거의 항상 포인터 리시버를 사용해야 합니다.
  • 성능: 큰 구조체는 매 메서드 호출 시 복사 비용을 피할 수 있습니다.
  • 인터페이스 구현: 포인터 리시버를 가진 메서드는 포인터 타입에만 속하므로, 타입이 인터페이스를 만족하는지에 영향을 줍니다.

Choosing Between Value and Pointer Receivers

SituationRecommended Receiver
메서드가 리시버의 필드를 수정해야 함포인터 리시버 (*T)
구조체가 크고 복사가 비용이 많이 듦포인터 리시버
메서드가 데이터를 읽기만 하고 구조체가 작음값 리시버 (T)
포인터 메서드가 필요한 인터페이스를 구현해야 함포인터 리시버

실제로 백엔드 시스템에서 실제 엔티티(서비스, 핸들러, 설정, DB 모델, 컨트롤러)를 나타내는 타입이라면 포인터 리시버가 기본 선택입니다. 불변 데이터처럼 동작하는 간단한 값 타입이라면 값 리시버도 괜찮습니다.

Summary

  • 값 리시버: 복사본에서 동작; 수정이 메서드 밖에 반영되지 않음.
  • 포인터 리시버: 원본에서 동작; 수정이 지속됨.
  • 상태를 수정하거나 큰 구조체 복사를 피하거나 포인터 메서드가 필요한 인터페이스를 만족해야 할 때는 포인터 리시버를 사용하세요.
  • 작고 읽기 전용인 타입에는 값 리시버를 사용하세요.

이 구분을 몸에 익히면 Go에서 메서드 설계가 훨씬 쉬워집니다.

Back to Blog

관련 글

더 보기 »

Go의 비밀스러운 삶: ‘defer’ 문

챕터 20: The Stacked Deck Ethan의 데스크탑 PC 팬이 크게 돌고 있었다. 그는 오류 메시지를 끊임없이 뿜어내는 터미널을 마치 부서진 불꽃처럼 바라보고 있었다.

Go의 비밀스러운 삶: Panic과 Recover

Mastering Panic, Recover, and the “Must” Pattern in Go Chapter 21: The Emergency Brake > “내가 뭘 망친 것 같은데,” 이든이 속삭였다. 그는 자신의 터미널을 바라보고 있었다.