메서드와 연관된 변수를 지칭한다.
리시버는 함수의 첫 번째 매개변수로서 정의되며, 해당 타입에 메서드를 연결하는 역할을 한다.
예시)
// func 키워드와 함수 이름 사이의 매개변수
func (s *service) InsertUser(user *entities.User) (*entities.User, error) {
return s.repository.CreateUser(user)
}
먼저 user 타입을 선언했다고 치자.
type user struct {
name string
email string
}
예시)
func (u user) notify() {
fmt.Printf("Sending User Email To %s<%s>\n",
u.name,
u.email)
}
notify
메서드의 수신자는 user
타입의 값으로 선언되었다.
이렇게 값 수신자에 정의된 메서드는 호출 시점에 항상 그 값의 복사본을 대상으로 실행된다.
func main() {
bill := user{"Bill", "bill@email.com"}
bill.notify()
}
user
타입의 변수 bill
을 선언한 후 이름과 메일 주소를 초기화했다.
이 bill 변수를 이용하여 notify
메서드를 호출하고 있다.
변수bill
의 값이 메서드 호출을 위한 수신자의 값이 되며, notify
메서드는 값의 복사본을 이용해 수행한다.
추가 예시)
포인터를 이용해서 값 수신자에 정의된 메서드를 호출할 수도 있다.
lisa := &user{"Lisa", "lisa@email.com"}
lisa.notify()
user
타입의 변수 lisa
를 선언한 후 이름과 메일 주소를 초기화한다.
포인터 변수를 이용하여 notify
메서드를 호출한다.
자세히 설명해보자면,
user{"Lisa", "lisa@email.com"}
: user
타입의 새로운 인스턴스를 생성한다.&user{"Lisa", "lisa@email.com"}
: &
연산자를 사용하여 해당 user
인스턴스의 메모리 주소를 가져온다.lisa := &user{"Lisa", "lisa@email.com"}
: 이 주소를 lisa
라는 변수에 할당한다. 따라서 lisa
는 user
타입의 포인터이다.비유하자면,
&user{"Lisa", "lisa@email.com"}
는 그 집의 "주소"lisa
변수는 그 주소를 저장하므로, 언제든지 집을 찾아갈 수 있다.
이때 메서드 호출이 가능하도록 하기 위해 Go는 포인터 값을 메서드의 수신자에 적합한 값으로 조정한다.
(*lisa).notify()
포인터 값을 역참조하여 값 수신자에 정의된 메서드를 호출할 수 있도록 돕는다.
예시)
func (u *user) changeEmail(email string) {
u.email = email
}
포인터 수신자에 정의한 메서드를 호출하면 메서드를 호출하기 위해 사용된 값을 메서드가 공유한다.
lisa := &user{"Lisa", "lisa@email.com"}
bill.changeEmail("bill@newdomain.com")
lisa
포인터 값을 선언한 후 changeEmail
메서드를 호출했다.
이 경우 changeEmail
메서드 내에서 lisa
포인터가 가리키는 값에 대해 이루어진 모든 변경 사항은 메서드 호출이 리턴된 이후에도 계속해서 유지된다. (포인터 수신자는 실제 값을 전달받는다.)
→ 이 점이 포인터 수신자의 장점이다.
bill := &user{"Bill", "bill@email.com"}
bill.changeEmail("bill@newdomain.com")
여기서는 bill 변수를 선언한 후 포인터 수신기에 선언된 changeEmail 메서드를 호출한다.
아까도 말했듯, Go는 포인터 값을 메서드의 수신자에 적합한 값으로 알아서 조정한다.
(&bill).changeEamil("bill@newsdomain.com")
이번 경우에는 값을 참조하여 메서드 호출에 적합한 수신자 타입으로 변환한다.
→ Go는 편리하게도 메서드의 본래 수신자와 일치하지 않는 값과 포인터를 이용해도 메서드를 호출할 수 있도록 허용하고 있다.
새로운 타입을 정의 했다고 가정하자.
이 타입의 값에 무언가를 더하거나 삭제한다면,
이 질문에 대한 답이 새로운 값이라면 메서드에 값 수신자, 기본 값의 변경이 답이라면 포인터 수신자를 사용하자.
따라서, 만약 메서드 내에서 구조체의 상태를 변경하는 작업이 필요한 경우에는 포인터 리시버를 사용하는 것이 좋다. 반대로, 구조체의 상태를 변경하지 않는 읽기 전용 작업을 수행하는 경우에는 값 리시버를 사용하는 것이 좋을 수 있다.
하지만, 메서드가 이 값으로 무엇을 수행하느냐보단 그 값의 본질에 집중하기를 권한다.
참고: Go 인 액션