X는 Y를 구현하지 않습니다 (… 메소드에 포인터 수신기가 있습니다).


201

이 " X는 Y를 구현하지 않습니다 (... 메소드에는 포인터 수신기가 있습니다 " ) .

따라서 질문을 매우 구체적으로 만드는 대신 광범위하고 추상적으로 만들고 있습니다.이 오류가 발생할 수있는 몇 가지 다른 경우가있는 것처럼 보입니다. 누군가 그것을 요약 해 주시겠습니까?

즉, 문제를 피하는 방법과 문제가 발생하면 어떤 가능성이 있습니까? 고마워.

답변:


365

이 컴파일 타임 오류는 구체적 유형을 인터페이스 유형 에 지정하거나 전달 (또는 변환)하려고 할 때 발생 합니다. 타입 자체는 인터페이스를 구현하지 않고 type에 대한 포인터 만 구현합니다 .

예를 보자.

type Stringer interface {
    String() string
}

type MyType struct {
    value string
}

func (m *MyType) String() string { return m.value }

Stringer인터페이스 유형은 하나의 방법 만이 있습니다 String(). 인터페이스 값에 저장된 모든 값 Stringer에는이 방법이 있어야합니다. 또한을 만들고 포인터 수신기를 사용 MyType하여 메서드 MyType.String()를 만들었습니다 . 이는 메소드가 유형 의 메소드 세트 에 있지만 의 메소드 세트 에는 없음을 의미 합니다.String()*MyTypeMyType

MyTypetype의 변수에 값을 할당하려고 Stringer하면 해당 오류가 발생합니다.

m := MyType{value: "something"}

var s Stringer
s = m // cannot use m (type MyType) as type Stringer in assignment:
      //   MyType does not implement Stringer (String method has pointer receiver)

우리는 유형의 값을 할당하려고한다면 모든 것이 괜찮 *MyType로를 Stringer:

s = &m
fmt.Println(s)

그리고 우리는 예상 결과를 얻습니다 ( Go Playground 에서 시도하십시오 ).

something

따라서이 컴파일 타임 오류를 얻는 데 필요한 요구 사항은 다음과 같습니다.

  • 포인터아닌 콘크리트 유형 의 값 이 지정되거나 전달되거나 변환됩니다.
  • 인터페이스 유형에 지정되거나 전달되거나 변환되는 인터페이스 유형
  • 구체적 유형에는 필요한 인터페이스 방법이 있지만 포인터 수신기가 있습니다.

문제를 해결할 수있는 가능성 :

  • 값에 대한 포인터를 사용해야하며, 이의 메소드 세트에는 포인터 수신기가있는 메소드가 포함됩니다.
  • 또는 리시버 유형을 non-pointer 로 변경해야 하므로 비 포인터 콘크리트 유형의 메소드 세트도 메소드를 포함하므로 인터페이스를 만족시킵니다. 메소드가 값을 수정해야하는 것처럼 비 포인터 리시버는 옵션이 아닌 것처럼 이는 실행 가능하거나 불가능할 수 있습니다.

구조 및 임베딩

구조체와 embedding을 사용할 때 , 종종 인터페이스를 구현하는 것은 "사용자"가 아니라 (메소드 구현을 제공함),에 포함 된 타입 struct입니다. 이 예에서와 같이 :

type MyType2 struct {
    MyType
}

m := MyType{value: "something"}
m2 := MyType2{MyType: m}

var s Stringer
s = m2 // Compile-time error again

다시 한 번, 컴파일 시간 오류는의 메소드 세트에 embed MyType2String()메소드를 포함하지 않기 때문에의 메소드 세트 MyType만 포함 *MyType2하므로 다음 작업이 수행됩니다 ( Go Playground 에서 시도 ).

var s Stringer
s = &m2

포인터*MyType아닌 포인터 를 포함 하고 사용하는 경우 MyType2에도 Go Playground 에서 시도해보십시오 .

type MyType2 struct {
    *MyType
}

m := MyType{value: "something"}
m2 := MyType2{MyType: &m}

var s Stringer
s = m2

또한 우리가 포함하거나 ( MyType또는 *MyType) 포인터를 사용 *MyType2하면 항상 작동합니다 ( Go Playground 에서 시도하십시오 ).

type MyType2 struct {
    *MyType
}

m := MyType{value: "something"}
m2 := MyType2{MyType: &m}

var s Stringer
s = &m2

스펙의 관련 섹션 (섹션 유형에서 ) :

구조체 유형 S과이라는 유형이 주어지면 T다음과 같이 승격 된 메소드가 구조체의 메소드 세트에 포함됩니다.

  • S익명 필드가 포함 된 경우 T메소드 세트 S*S둘 다 수신자와 함께 승격 된 메소드를 포함 T합니다. 의 메소드 세트 *S에는 수신자와 함께 승격 된 메소드도 포함됩니다 *T.
  • 만약이 S익명의 필드 포함 *T, 방법 세트를 S하고 *S모두 수신기 방법 승진 포함 T또는 *T.

다시 말해, 비 포인터 타입을 임베드하면 비 포인터 임 베더의 메소드 세트는 비 포인터 리시버가있는 메소드 만 임베드 된 유형에서 가져옵니다.

포인터 유형을 포함 시키면 비 포인터 임 베더의 메소드 세트는 포인터 및 비 포인터 수신자를 모두 포함하는 메소드를 포함합니다 (임베디드 유형에서).

임베디드 타입이 포인터인지 아닌지에 관계없이 임 베더에 포인터 값을 사용하는 경우, 임 베더에 대한 포인터의 메소드 세트는 항상 임베디드 타입의 포인터와 비 포인터 수신자 모두를 가진 메소드를 얻는다.

노트 :

매우 유사한 경우가 있습니다. 즉, 값을 래핑하는 인터페이스 값이 MyType있고 다른 인터페이스 값 을 입력 하려고하면 Stringer. 이 경우 어설 션은 위에서 설명한 이유로 유지되지 않지만 약간 다른 런타임 오류가 발생합니다.

m := MyType{value: "something"}

var i interface{} = m
fmt.Println(i.(Stringer))

런타임 패닉 ( Go Playground 에서 시도 ) :

panic: interface conversion: main.MyType is not main.Stringer:
    missing method String

assert 타입 대신 변환하려고하면, 우리가 이야기하고있는 컴파일 타임 에러가 발생합니다 :

m := MyType{value: "something"}

fmt.Println(Stringer(m))

매우 포괄적 인 답변에 감사드립니다. 이상하게 응답하여 죄송합니다. SO 알림을받지 못했습니다. 내가 검색 한 경우의 대답은 "멤버 함수"가 모든 포인터 유형 (예 : " func (m *MyType)") 또는 none 이어야한다는 것 입니다. 그렇습니까? func (m *MyType)& 같은 다른 유형의 "멤버 함수"를 혼합 할 수 있습니까 func (m MyType)?
xpt

3
@xpt 포인터와 비 포인터 리시버를 혼합 할 수 있지만, 모두 동일하게 만들 필요는 없습니다. 포인터 수신기가있는 19 개의 메서드가 있고 포인터가 아닌 수신기로 메서드를 만드는 경우에는 이상합니다. 또한 혼합을 시작하면 어떤 방법이 어떤 유형의 방법 세트에 포함되는지 추적하기가 더 어려워집니다. 이 답변에 대한 자세한 내용 : Golang의 Value Receiver와 Pointer Receiver?
icza

값 메소드를 사용하도록 MyType변경할 수없는 경우 인터페이스 {}로 값을 래핑하여 "참고 :"의 끝에 언급 된 문제를 실제로 어떻게 해결합니까? MyType나는 이것과 같은 것을 시도했지만 (&i).(*Stringer)작동하지 않았다. 가능합니까?
Joel Edström

1
@ JoelEdström 예, 가능하지만 거의 이해가되지 않습니다. 예를 들어, 비 포인터 타입의 값을 타입 어설 션하고 변수에 저장할 x := i.(MyType)수 있습니다. 예를 들어 , 포인터 리시버가있는 메소드를 호출 할 수 있습니다 (예 : 변수의 주소를 지정할 수 있기 때문에 성공 i.String()하는 약칭) (&i).String(). 그러나 값 (지정된 값)을 변경하는 포인터 방법은 인터페이스 값에 래핑 된 값에 반영되지 않으므로 이치에 맞지 않습니다.
icza

1
@DeepNightTwo 주소 지정이 불가능할 수 있기 때문에 (예 : 함수 반환 값 또는 맵 인덱싱 결과) 메소드 사본에 메소드 *T가 포함 되지 않으며, 종종 사본 만 존재 / 수신되므로 주소를 가져 오는 것이 허용되는 경우 메소드 포인터 수신기를 사용하면 사본 만 수정할 수 있습니다 (원본이 수정되었다고 가정하면 혼란). 리플렉션 SetString 사용 예제는이 답변을 참조하십시오 . SS
icza

33

짧게 유지하려면이 코드가 있고이 인터페이스를 구현하는 Loader 인터페이스와 WebLoader가 있다고 가정하십시오.

package main

import "fmt"

// Loader defines a content loader
type Loader interface {
    Load(src string) string
}

// WebLoader is a web content loader
type WebLoader struct{}

// Load loads the content of a page
func (w *WebLoader) Load(src string) string {
    return fmt.Sprintf("I loaded this page %s", src)
}

func main() {
    webLoader := WebLoader{}
    loadContent(webLoader)
}

func loadContent(loader Loader) {
    loader.Load("google.com")
}

따라서이 코드는이 컴파일 시간 오류를 줄 것입니다

./main.go:20:13 : loadContent의 인수에서 webLoader (유형 WebLoader)를 Loader 유형으로 사용할 수 없음 : WebLoader가 Loader를 구현하지 않음 (Load 메소드에 포인터 수신자가 있음)

따라서 당신이해야 할 일은 webLoader := WebLoader{}다음 으로 변경 하는 것입니다.

webLoader := &WebLoader{} 

func (w *WebLoader) Load포인터 리시버를 받아들이도록 이 함수 를 정의하기 때문에 왜 수정 될까요? 자세한 설명은 @icza 및 @karora 답변을 참조하십시오


6
지금까지 이것은 이해하기 가장 쉬운 의견이었습니다. 그리고 내가 직면 한 문제를 직접 해결했습니다.
Maxs728

@ Maxs728 많은 Go 문제에 대한 답변이 흔하지 않습니다.
milosmns

6

이런 종류의 일이 발생하는 것을 본 또 다른 경우는 일부 메소드가 내부 값을 수정하고 다른 메소드는 수정하지 않는 인터페이스를 작성하려는 경우입니다.

type GetterSetter interface {
   GetVal() int
   SetVal(x int) int
}

이 인터페이스를 구현하는 것은 다음과 같습니다.

type MyTypeA struct {
   a int
}

func (m MyTypeA) GetVal() int {
   return a
}

func (m *MyTypeA) SetVal(newVal int) int {
    int oldVal = m.a
    m.a = newVal
    return oldVal
}

구현 유형에는 포인터 수신기 인 메소드와 그렇지 않은 메소드가있을 수 있으며 GetterSetters와 같은 다양한 다양한 기능을 가지고 있기 때문에 테스트에서 모두 예상대로 수행하고 싶습니다.

내가 이런 식으로해야한다면 :

myTypeInstance := MyType{ 7 }
... maybe some code doing other stuff ...
var f interface{} = myTypeInstance
_, ok := f.(GetterSetter)
if !ok {
    t.Fail()
}

그런 다음 위에서 언급 한 "X는 Y를 구현하지 않습니다 (Z 메서드에는 포인터 수신기가 있습니다)"오류 (컴파일 타임 오류 이므로) 내 테스트가 실패한 이유를 정확하게 파악 하지 못하는 날 있습니다. .

대신 포인터를 사용하여 유형 검사를 수행해야합니다.

var f interface{} = new(&MyTypeA)
 ...

또는:

myTypeInstance := MyType{ 7 }
var f interface{} = &myTypeInstance
...

그런 다음 모든 테스트에 만족합니다!

하지만 기다려! 내 코드에는 어딘가에 GetterSetter를 허용하는 메소드가 있습니다.

func SomeStuff(g GetterSetter, x int) int {
    if x > 10 {
        return g.GetVal() + 1
    }
    return g.GetVal()
}

다른 유형의 메소드에서 이러한 메소드를 호출하면 오류가 발생합니다.

func (m MyTypeA) OtherThing(x int) {
    SomeStuff(m, x)
}

다음 통화 중 하나가 작동합니다.

func (m *MyTypeA) OtherThing(x int) {
    SomeStuff(m, x)
}

func (m MyTypeA) OtherThing(x int) {
    SomeStuff(&m, x)
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.