수신자와 인터페이스

값 수신자를 가진 메서드는 값과 포인터를 사용하여 호출할 수 있습니다.

포인터 수신자를 가진 메서드는 포인터 또는 주소 가능한 값을 사용하여 호출할 수 있습니다.

예를 들어,

type S struct {
  data string
}

func (s S) Read() string {
  return s.data
}

func (s *S) Write(str string) {
  s.data = str
}

sVals := map[int]S{1: {"A"}}

// 값만을 사용하여 Read를 호출할 수 있습니다
sVals[1].Read()

// 다음은 컴파일되지 않습니다:
// sVals[1].Write("test")

sPtrs := map[int]*S{1: {"A"}}

// Read와 Write 메서드 모두 포인터를 사용하여 호출할 수 있습니다
sPtrs[1].Read()
sPtrs[1].Write("test")

마찬가지로, 메서드가 값을 수신자를 가지고 있더라도 포인터 수신자를 사용하여 인터페이스를 충족시킬 수 있습니다.

type F interface {
  f()
}

type S1 struct{}

func (s S1) f() {}

type S2 struct{}

func (s *S2) f() {}

s1Val := S1{}
s1Ptr := &S1{}
s2Val := S2{}
s2Ptr := &S2{}

var i F
i = s1Val
i = s1Ptr
i = s2Ptr

// 다음 코드는 s2Val이 값이며 S2의 f 메서드가 값을 수신자로 가지지 않기 때문에 컴파일되지 않습니다
// i = s2Val

Effective Go에는 포인터 대 값에 대한 훌륭한 설명이 있습니다.

추가 사항:

  • 유형은 값 수신자를 가진 메서드 집합과 포인터 수신자를 가진 메서드 집합을 가질 수 있습니다.
    • 값 수신자를 가진 메서드 집합은 포인터 수신자를 가진 메서드 집합의 하위 집합이지만 그 역은 성립하지 않습니다.
  • 규칙
    • 값 객체는 값 수신자를 가진 메서드 집합만 사용할 수 있습니다.
    • 포인터 객체는 값 수신자를 가진 메서드 집합과 포인터 수신자를 가진 메서드 집합을 모두 사용할 수 있습니다.
  • 인터페이스 일치 (또는 구현)
    • 유형은 인터페이스의 모든 메서드를 구현하면 해당 인터페이스와 일치합니다.
    • 구체적으로, 유형의 값 메서드 집합이 인터페이스와 일치하거나 포인터 메서드 집합이 인터페이스와 일치합니다.

두 가지 특정 일치 유형이 있습니다:

  • 값 메서드 집합이 인터페이스와 일치
    • 값 또는 포인터 객체를 인터페이스 변수에 할당하는 것은 괜찮습니다. 왜냐하면 둘 다 값을 메서드 집합을 포함하기 때문입니다.
  • 포인터 메서드 집합이 인터페이스와 일치
    • 포인터 객체만 인터페이스 변수에 할당될 수 있으며 포인터 메서드 집합만 인터페이스와 일치하기 때문입니다.
    • 값 객체가 인터페이스 변수에 할당되면 컴파일 중에 인터페이스 생존성 검사 메커니즘이 트리거됩니다.

s2Val에 대한 i = s2Val이 오류를 트리거하는 이유는 값 메서드 집합과 인터페이스가 일치하지 않기 때문입니다.