Geben Sie konvertierende Schnittstellenschnitte ein


194

Ich bin neugierig , warum implizit Go does't konvertieren []Tzu , []interface{}wenn es implizit konvertieren Tzu interface{}. Gibt es etwas nicht Triviales an dieser Konvertierung, das mir fehlt?

Beispiel:

func foo([]interface{}) { /* do something */ }

func main() {
    var a []string = []string{"hello", "world"}
    foo(a)
}

go build beschwert sich

kann keine (Typ [] Zeichenfolge) als Typ [] Schnittstelle {} im Funktionsargument verwenden

Und wenn ich es explizit versuche, dasselbe: sich b := []interface{}(a)beschweren

Ein (Typ [] String) kann nicht in Typ [] Schnittstelle {} konvertiert werden

Jedes Mal, wenn ich diese Konvertierung durchführen muss (was sehr häufig vorkommt), habe ich so etwas gemacht:

b = make([]interface{}, len(a), len(a))
for i := range a {
    b[i] = a[i]
}

Gibt es eine bessere Möglichkeit, dies zu tun, oder Standardbibliotheksfunktionen, um bei diesen Konvertierungen zu helfen? Es scheint irgendwie albern, jedes Mal, wenn ich eine Funktion aufrufen möchte, die eine Liste von z. B. Ints oder Strings aufnehmen kann, 4 zusätzliche Codezeilen zu schreiben.


Sie definieren also "implizit [] T in [] Schnittstelle {} konvertieren" als "Zuweisen eines neuen Slice und Kopieren aller Elemente". Dies steht im Widerspruch zu dem, was "T implizit in Schnittstelle {} konvertieren" bewirkt. Dies ist nur eine Ansicht mit demselben Wert wie ein allgemeinerer statischer Typ. nichts wird kopiert; und wenn Sie es wieder auf Typ T setzen, erhalten Sie immer noch dasselbe zurück.
Newacct

1
Ich habe nicht zu detailliert darüber nachgedacht, wie es implementiert werden soll, nur dass es auf einer höheren Ebene praktisch wäre, eine ganze Liste einfach in einen anderen Typ konvertieren zu können, um das Problem zu umgehen, wenn keine generischen Typen vorhanden sind.
Danny

Antworten:


215

In Go gibt es eine allgemeine Regel, nach der die Syntax komplexe / kostspielige Vorgänge nicht verbergen soll. Die Konvertierung von a stringin a interface{}erfolgt in O (1) -Zeit. Das Konvertieren von a []stringin a interface{}erfolgt ebenfalls in O (1) -Zeit, da ein Slice immer noch einen Wert hat. Die Konvertierung von a []stringin a []interface{}ist jedoch O (n), da jedes Element des Slice in eine konvertiert werden muss interface{}.

Die einzige Ausnahme von dieser Regel ist das Konvertieren von Zeichenfolgen. Beim Konvertieren von a stringnach und von a []byteoder a []runefunktioniert Go mit O (n), obwohl Konvertierungen "Syntax" sind.

Es gibt keine Standardbibliotheksfunktion, die diese Konvertierung für Sie durchführt. Sie könnten eine mit Reflect erstellen, diese ist jedoch langsamer als die Option mit drei Zeilen.

Beispiel mit Reflexion:

func InterfaceSlice(slice interface{}) []interface{} {
    s := reflect.ValueOf(slice)
    if s.Kind() != reflect.Slice {
        panic("InterfaceSlice() given a non-slice type")
    }

    ret := make([]interface{}, s.Len())

    for i:=0; i<s.Len(); i++ {
        ret[i] = s.Index(i).Interface()
    }

    return ret
}

Ihre beste Option ist jedoch, nur die Codezeilen zu verwenden, die Sie in Ihrer Frage angegeben haben:

b := make([]interface{}, len(a))
for i := range a {
    b[i] = a[i]
}

3
es könnte langsamer sein, aber es würde generisch mit jeder Art von Slice
funktionieren

Diese ganze Antwort gilt mapübrigens auch für s.
RickyA

Dies gilt auch für channels.
Justin Ohms

Vielen Dank für die klare Erklärung, wenn möglich können Sie ref hinzufügen. für Converting a []string to an interface{} is also done in O(1) time?
Mayur

56

Das , was Sie fehlt, ist , dass Tund interface{}die einen Wert von hält Thaben unterschiedliche Darstellungen im Speicher so nicht triviale Weise umgewandelt werden können.

Eine Variable vom Typ Tist nur ihr Wert im Speicher. Es sind keine Typinformationen zugeordnet (in Go hat jede Variable einen einzelnen Typ, der zur Kompilierungszeit und nicht zur Laufzeit bekannt ist). Es wird im Gedächtnis wie folgt dargestellt:

  • Wert

Ein interface{}Halten einer Variablen vom Typ Twird im Speicher wie folgt dargestellt

  • Zeiger auf Typ T
  • Wert

Kommen wir also zu Ihrer ursprünglichen Frage zurück: Warum konvertiert go nicht implizit []Tzu []interface{}?

Das Konvertieren []Tin []interface{}würde das Erstellen einer neuen Wertscheibe umfassen interface {}, was nicht trivial ist, da das speicherinterne Layout völlig anders ist.


10
Dies ist informativ und gut geschrieben (+1), aber Sie sprechen nicht den anderen Teil seiner Frage an: "Gibt es einen besseren Weg, dies zu tun ..." (-1).
weberc2

13

Hier ist die offizielle Erklärung: https://github.com/golang/go/wiki/InterfaceSlice

var dataSlice []int = foo()
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
for i, d := range dataSlice {
    interfaceSlice[i] = d
}

Gute Infos, aber keine offizielle Erklärung. Es ist ein Wiki, in dem jeder bearbeiten kann.
Inanc Gumus

Wie soll ich []interfacein den Typ konvertieren , den ich erwarte, wie z []map[string]interface{}.
Lewis Chan

8

Versuchen Sie es interface{}stattdessen. Versuchen Sie es, um es als Scheibe zurückzuwerfen

func foo(bar interface{}) {
    s := bar.([]string)
    // ...
}

3
Dies wirft die nächste Frage auf: Wie soll das OP dann durchlaufen bar, um es als "Slice jeglichen Typs" zu interpretieren? Beachten Sie, dass sein Dreiliner eine []interface{}, keine []stringoder eine Scheibe eines anderen Betontyps erzeugt.
Kostix

13
-1: Dies funktioniert nur, wenn bar [] string ist. In diesem Fall können Sie auch schreiben:func foo(bar []string) { /* ... */ }
weberc2

2
Verwenden Sie einen Typschalter oder Reflektion wie in der akzeptierten Antwort.
Dskinner

OP muss seinen Typ zur Laufzeit auf jeden Fall herausfinden. Wenn Sie kein Slice verwenden interface{}, beschwert sich der Compiler nicht, wenn Sie ein Argument wie in übergeben foo(a). Er könnte entweder Reflect oder Type Switching ( golang.org/doc/effective_go.html#type_switch ) verwenden foo.
Cassiohg

2

interface{}In einen beliebigen Typ konvertieren .

Syntax:

result := interface.(datatype)

Beispiel:

var employee interface{} = []string{"Jhon", "Arya"}
result := employee.([]string)   //result type is []string.

2
Bist du dir sicher? Ich denke nicht, dass dies gültig ist. Sie werden es sehen: invalid type assertion: potato.([]string) (non-interface type []interface {} on left)
Adriano Tadao

2

Falls Sie mehr Kurzschluss für Ihren Code benötigen, können Sie einen neuen Typ für den Helfer erstellen

type Strings []string

func (ss Strings) ToInterfaceSlice() []interface{} {
    iface := make([]interface{}, len(ss))
    for i := range ss {
        iface[i] = ss[i]
    }
    return iface
}

dann

a := []strings{"a", "b", "c", "d"}
sliceIFace := Strings(a).ToInterfaceSlice()
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.