Call Go function that accepts a slice of interface A with a slice of struct B (B implements A)

匆匆过客 提交于 2019-12-10 14:54:49

问题


I have the following types:

type Statement interface {
    Say() string
}

type Quote struct {
    quote string
}

func (p Quote) Say() string {
    return p.quote
}

func Replay(conversation []Statement) {
    for _, statement := range conversation {
        fmt.Println(statement.Say())
    }
}

I think I have a fairly good grasp of why a function that accepts a parameter of type []Statement, cannot be called with []Quote; even though Quote implements Statement, []Quote does not implement []Statement. []Statement is not even an interface. It has the type slice of Statement. While Go implicitly converts from a type to an interface type, it does no implicit conversion from a slice of type A to a slice of interface B.

We can convert the quotes to statements explicitly:

conversation := []Quote{
    Quote{"Nice Guy Eddie: C'mon, throw in a buck!"},
    Quote{"Mr. Pink: Uh-uh, I don't tip."},
    Quote{"Nice Guy Eddie: You don't tip?"},
    Quote{"Mr. Pink: Nah, I don't believe in it."},
    Quote{"Nice Guy Eddie: You don't believe in tipping?"},
}

// This doesn't work
// Replay(conversation)

// Create statements from quotes
statements := make([]Statement, len(conversation))
for i, quote := range conversation {
    statements[i] = quote
}

Replay(statements)

Now say that Replay is part of a library that wants to go out of its way in how easy it's to use Replay. It allows you to call Replay with any slice of objects as long as those objects implement the Statement interface. To do so it has the following conversion method:

func ConvertToStatements(its interface{}) ([]Statement, error) {
    itsValue := reflect.ValueOf(its)
    itsKind := itsValue.Kind()
    if itsKind != reflect.Array && itsKind != reflect.Slice {
        return nil, fmt.Errorf("Expected items to be an Array or a Slice, got %s", itsKind)
    }
    itsLength := itsValue.Len()
    items := make([]Statement, itsLength)
    for i := 0; i < itsLength; i++ {
        itsItem := itsValue.Index(i)
        if item, ok := itsItem.Interface().(Statement); ok {
            items[i] = item
        } else {
            return nil, fmt.Errorf("item #%d does not implement the Statement interface: %s", i, itsItem)
        }
    }
    return items, nil
}

Replay looks like this:

func Replay(its interface{}) {
    conversation := ConvertToStatements(its)
    for _, statement := range conversation {
        fmt.Println(statement.Say())
    }
}

We can now call Replay with quotes directly:

Replay(conversation)

Finally, my question: Is there a simpler way to allow Replay to accept a slice of any type A, as long as A implements the Statement interface?


回答1:


The in-memory layout of a []Quote slice is different to a []Statement slice, so this is not possible.

The backing array of the []Quote slice will consist of the sequential Quote structs, while the []Statement slice's backing array consists of interface variables. As well as holding the Quote struct (or whatever other type implements the interface), the interface variable also stores a pointer to type information for the contained value. This is needed to determine how to dispatch the Say method call.

The different data layout means that you can't interchange the two slice types, not even through unsafe casts: if you have one type and need the other you'll need to manually convert between them.




回答2:


The very short answer to your (long) question is: No.

I don't think that your solution of ConvertToStatment and Replay taking an empty interface is a "nice" solution: I'd prefer func Replay([]Statement) and callers must provide a slice of Statments. This is much clearer and callers can either convert their stuff to []Statement or directly construct a []Statement.




回答3:


The following code has two different structure types that both implement the Say() function. You can create an array containing both types and call Replay()and have it do what you want:

package main

import "fmt"

type Statement interface {
    Say() string
}
type Statements []Statement

type Quote struct {
    quote string
}
type Quotes []Quote

func (p Quote) Say() string {
    return p.quote
}

type Attributed struct {
    who   string
    quote string
}

func (p Attributed) Say() string {
    return p.who + ": " + p.quote
}


func Replay(conversation []Statement) {
    for _, s := range conversation {
        fmt.Println(s.Say())
    }
}

func (q Quotes) toStatements() Statements {
    conv := make(Statements, len(q))
    for i, v := range q {
        conv[i] = Statement(v)
    }
    return conv
}

func main() {
    conversation := Statements{
        Quote{"Nice Guy Eddie: C'mon, throw in a buck!"},
        Quote{"Mr. Pink: Uh-uh, I don't tip."},
        Attributed{"Nice Guy Eddie", "You don't tip?"},  // <= another type
        Quote{"Mr. Pink: Nah, I don't believe in it."},
        Quote{"Nice Guy Eddie: You don't believe in tipping?"},
    }

    myquotes := Quotes{
        Quote{"Nice Guy Eddie: C'mon, throw in a buck!"},
        Quote{"Mr. Pink: Uh-uh, I don't tip."},
        Quote{"Nice Guy Eddie: You don't tip?"},
        Quote{"Mr. Pink: Nah, I don't believe in it."},
        Quote{"Nice Guy Eddie: You don't believe in tipping?"},
    }

    Replay(conversation)
    Replay(myquotes.toStatements())
}

Replay() doesn't change or know anything about Attributed{}. You do have to introduce types for the slices Quotes & Statements.



来源:https://stackoverflow.com/questions/20163660/call-go-function-that-accepts-a-slice-of-interface-a-with-a-slice-of-struct-b-b

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!