"<type>은 인터페이스가 아니라 인터페이스에 대한 포인터입니다."혼동


104

동료 개발자 여러분,

나에게 조금 이상해 보이는 문제가 있습니다. 이 코드 스 니펫을 살펴보십시오.

package coreinterfaces

type FilterInterface interface {
    Filter(s *string) bool
}

type FieldFilter struct {
    Key string
    Val string
}

func (ff *FieldFilter) Filter(s *string) bool {
    // Some code
}

type FilterMapInterface interface {
    AddFilter(f *FilterInterface) uuid.UUID     
    RemoveFilter(i uuid.UUID)                   
    GetFilterByID(i uuid.UUID) *FilterInterface
}

type FilterMap struct {
    mutex   sync.Mutex
    Filters map[uuid.UUID]FilterInterface
}

func (fp *FilterMap) AddFilter(f *FilterInterface) uuid.UUID {
    // Some code
}

func (fp *FilterMap) RemoveFilter(i uuid.UUID) {
    // Some code
}

func (fp *FilterMap) GetFilterByID(i uuid.UUID) *FilterInterface {
    // Some code
}

다른 패키지에는 다음 코드가 있습니다.

func DoFilter() {
    fieldfilter := &coreinterfaces.FieldFilter{Key: "app", Val: "152511"}
    filtermap := &coreinterfaces.FilterMap{}
    _ = filtermap.AddFilter(fieldfilter) // <--- Exception is raised here
}

런타임은 언급 된 줄을 받아들이지 않을 것입니다.

"fieldint.AddFilter에 대한 인수에서 * coreinterfaces.FilterInterface 유형으로 fieldfilter (유형 * coreinterfaces.FieldFilter)를 사용할 수 없습니다. * coreinterfaces.FilterInterface는 인터페이스가 아닌 인터페이스에 대한 포인터입니다."

그러나 코드를 다음과 같이 변경할 때 :

func DoBid() error {
    bs := string(b)
    var ifilterfield coreinterfaces.FilterInterface
    fieldfilter := &coreinterfaces.FieldFilter{Key: "app", Val: "152511"}
    ifilterfield = fieldfilter
    filtermap := &coreinterfaces.FilterMap{}
    _ = filtermap.AddFilter(&ifilterfield)
}

모든 것이 정상이며 응용 프로그램을 디버깅 할 때 실제로 포함되는 것 같습니다.

이 주제에 대해 약간 혼란 스럽습니다. 이 동일한 문제를 논의 다른 블로그 게시물 및 스택 오버플로 스레드를 볼 때 (예 - , 또는 ) fieldmap fieldfilter 모두가 인터페이스 포인터로 초기화되기 때문에 오히려 값보다,이 예외가 작동합니다 제기 최초의 조각 인터페이스. FieldInterface를 선언하지 않고 해당 인터페이스에 대한 구현을 할당하지 않도록 변경해야하는 여기서 실제로 발생하는 일에 대해 머리를 감쌀 수 없었습니다. 이를위한 우아한 방법이 있어야합니다.


변경하는 경우 * FilterInterfaceFilterInterface_ = filtermap.AddFilter(fieldfilter)지금이 제기 coreinterfaces.FieldFilter가 coreinterfaces.FilterInterface를 구현하지 않습니다 (필터 방법은 포인터 수신기를 가지고) : filtermap.AddFilter에 인수 형 coreinterfaces.FilterInterface로 (유형 coreinterfaces.FieldFilter) fieldfilter 사용할 수 없습니다 을 변경하는 경우에는 라인이 _ = filtermap.AddFilter(&fieldfilter)작동합니다. 여기서 무슨 일이 일어나나요? 왜 그런 겁니까?
0rka

2
인터페이스를 구현하는 메서드에는 포인터 수신기가 있기 때문입니다. 값을 전달하면 인터페이스를 구현하지 않습니다. 포인터를 전달하면 메서드가 적용되기 때문입니다. 일반적으로 인터페이스를 다룰 때 인터페이스를 필요로하는 함수에 대한 포인터를 구조체에 전달합니다. 어떤 시나리오에서도 인터페이스에 대한 포인터를 거의 원하지 않습니다.
Adrian

1
나는 당신의 요점을 이해하지만 매개 변수 값을 * FilterInterface이 인터페이스를 구현하는 구조체 로 변경하면 인터페이스를 함수로 전달하는 아이디어가 깨집니다. 내가 내가 통과 있었는지 구조체에 결합되지 않는 달성하고 싶었지만 오히려 모든 구현은 내가 관심 인터페이스가 사용하는 것을 구조체. 코드 변경이 더 효율적이라고 생각하거나 제가 수행 할 수있는 표준에 부합한다고 생각하십니까? 코드 리뷰 서비스를 사용하게되어 기쁩니다. :)
0rka

2
함수는 인터페이스 인수를 받아야합니다 (인터페이스에 대한 포인터가 아님). 호출자는 인터페이스를 구현하는 구조체에 대한 포인터를 전달해야합니다. 이것은 "인터페이스를 함수로 전달하는 개념을 깨뜨리지"않습니다. 함수는 여전히 인터페이스를 취하고 인터페이스를 구현하는 결론을 전달합니다.
Adrian

답변:


140

그래서 여기서 두 가지 개념을 혼동하고 있습니다. 구조체에 대한 포인터와 인터페이스에 대한 포인터는 동일하지 않습니다. 인터페이스는 직접 구조체 중 하나를 저장할 수 있습니다 또는 구조체에 대한 포인터를. 후자의 경우에도 인터페이스에 대한 포인터가 아니라 인터페이스를 직접 사용합니다 . 예를 들면 :

type Fooer interface {
    Dummy()
}

type Foo struct{}

func (f Foo) Dummy() {}

func main() {
    var f1 Foo
    var f2 *Foo = &Foo{}

    DoFoo(f1)
    DoFoo(f2)
}

func DoFoo(f Fooer) {
    fmt.Printf("[%T] %+v\n", f, f)
}

산출:

[main.Foo] {}
[*main.Foo] &{}

https://play.golang.org/p/I7H_pv5H3Xl

두 경우 모두, f변수의 DoFoo단지 인터페이스입니다 하지 인터페이스에 대한 포인터. 기억 그러나 f2, 인터페이스는 유지 (A)에 대한 포인터Foo 구조에 .

인터페이스에 대한 포인터는 거의 없습니다 유용 . 실제로 Go 런타임은 구조 포인터의 경우처럼 인터페이스 포인터를 더 이상 자동으로 역 참조하지 않도록 일부 버전에서 특별히 변경되어 사용을 막았습니다. 대부분의 경우 인터페이스에 대한 포인터는 인터페이스의 작동 방식에 대한 오해를 반영합니다.

그러나 인터페이스에는 제한이 있습니다. 구조를 인터페이스에 직접 전달하는 경우 해당 유형의 메서드 (예 : func (f Foo) Dummy()아님 func (f *Foo) Dummy()) 만 인터페이스를 수행하는 데 사용할 수 있습니다. 이는 인터페이스에 원래 구조의 복사본을 저장하기 때문입니다. 따라서 포인터 메서드는 예상치 못한 영향을 미칠 수 있습니다 (즉, 원래 구조를 변경할 수 없음). 따라서 기본 경험 규칙은 특별한 이유가없는 한 인터페이스에 구조체에 대한 포인터저장 하는 것입니다.

특히 코드와 함께 AddFilter 함수 서명을 다음과 같이 변경하는 경우

func (fp *FilterMap) AddFilter(f FilterInterface) uuid.UUID

그리고 GetFilterByID 서명은 다음과 같습니다.

func (fp *FilterMap) GetFilterByID(i uuid.UUID) FilterInterface

코드가 예상대로 작동합니다. fieldfilter유형 인 *FieldFilterfullfills, FilterInterface따라서 인터페이스 유형을, 그리고 AddFilter그것을 받아 들일 것입니다.

다음은 Go에서 메서드, 유형 및 인터페이스가 작동하고 서로 통합되는 방식을 이해하기위한 몇 가지 좋은 참고 자료입니다.


"이것은 인터페이스에 원래 구조의 복사본을 저장하기 때문입니다. 따라서 포인터 메서드는 예상치 못한 효과 (즉, 원래 구조를 변경할 수 없음)를 가질 수 있습니다."-이것은 제한의 이유가 아닙니다. 결국, 유일한 사본이 인터페이스에 모두 저장되었을 수 있습니다.
WPWoodJr

당신의 대답은 의미가 없습니다. 인터페이스에 저장된 구체적인 유형이 거기에 저장된 내용을 변경할 때 변경되지 않는다고 가정하고 있으며, 이는 사실이 아니며 다른 메모리 레이아웃으로 무언가를 저장하는 경우 분명해야합니다. 내 포인터 주석에 대해 이해하지 못하는 것은 구체적인 유형의 포인터 수신기 메서드가 항상 호출되는 수신기를 수정할 수 있다는 것입니다. 인터페이스에 저장된 값은 참조를 가져올 수없는 사본을 강제 실행하므로 포인터 수신자 원래 기간을 수정할 수 없습니다 .
Kaedys

5
GetFilterByID(i uuid.UUID) *FilterInterface

이 오류가 발생하면 일반적으로 인터페이스 대신 인터페이스에 대한 포인터를 지정하기 때문입니다 (실제로는 인터페이스를 수행하는 내 구조체에 대한 포인터가됩니다).

* interface {...}에 대한 유효한 사용이 있지만 더 일반적으로 '이것은 내가 작성중인 코드의 포인터가되는 인터페이스입니다'대신 '이것은 포인터입니다'라고 생각합니다.

받아 들여진 답변이 상세하지만 문제 해결에 도움이되지 않았기 때문에 그냥 던졌습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.