Wpisz konwertujące plastry interfejsów

194

Jestem ciekaw dlaczego Go does't niejawnie przekonwertować []Tdo []interface{}kiedy będzie niejawnie konwertować Tdo interface{}. Czy w tej konwersji brakuje czegoś nietrywialnego?

Przykład:

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

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

go build narzeka

nie można użyć (typu [] ciągu) jako interfejsu typu [] {} w argumencie funkcji

A jeśli spróbuję to zrobić wyraźnie, to samo: b := []interface{}(a)narzeka

nie można przekonwertować ciągu (typ []) na interfejs typu [] {}

Więc za każdym razem, gdy muszę wykonać tę konwersję (która wydaje się często pojawiać), robiłem coś takiego:

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

Czy jest lepszy sposób, aby to zrobić, lub standardowe funkcje biblioteki, aby pomóc w tych konwersjach? Głupio wydaje się pisać 4 dodatkowe wiersze kodu za każdym razem, gdy chcę wywołać funkcję, która może przyjąć listę np. Ints lub łańcuchów.

Danny
źródło
więc definiujesz „domyślnie konwertuj [] T na [] interfejs {}”, co oznacza „przydzielanie nowego wycinka i kopiowanie wszystkich elementów”. Jest to niespójne z tym, co robi „niejawnie konwersja T do interfejsu {}”, co jest po prostu widokiem tej samej wartości, co bardziej ogólny typ statyczny; nic nie jest kopiowane; a jeśli wpiszesz go z powrotem do typu T, nadal otrzymasz to samo.
newacct
1
nie myślał zbyt szczegółowo o tym, jak zostanie zaimplementowany, po prostu na wyższym poziomie wygodnie byłoby łatwo przekonwertować całą listę na inny typ jako obejście dla braku typów ogólnych.
danny

Odpowiedzi:

215

W Go obowiązuje ogólna zasada, że ​​składnia nie powinna ukrywać skomplikowanych / kosztownych operacji. Konwersja a stringna an interface{}odbywa się w czasie O (1). Konwersja a []stringna an interface{}odbywa się również w czasie O (1), ponieważ plasterek jest nadal jedną wartością. Jednak konwersja a []stringna an []interface{}to czas O (n), ponieważ każdy element wycinka musi zostać przekonwertowany na an interface{}.

Jedynym wyjątkiem od tej reguły jest konwersja ciągów. Podczas konwersji a stringna a []bytelub a []runeGo działa O (n), nawet jeśli konwersje są „składniowe”.

Nie ma standardowej funkcji biblioteki, która wykonałaby tę konwersję za Ciebie. Możesz zrobić jeden z odbiciem, ale byłby wolniejszy niż opcja trzech linii.

Przykład z refleksją:

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
}

Najlepszą opcją jest jednak użycie wierszy kodu podanych w pytaniu:

b := make([]interface{}, len(a))
for i := range a {
    b[i] = a[i]
}
Stephen Weinberg
źródło
3
może być wolniejszy, ale działałby ogólnie z każdym rodzajem plastra
newacct
Cała odpowiedź dotyczy maps zbyt btw.
RickyA
Dotyczy to channelsrównież również.
Justin Ohms,
Dzięki za jasne wyjaśnienie, jeśli to możliwe, możesz dodać ref. dla Converting a []string to an interface{} is also done in O(1) time?
Mayur
56

Brakuje tego, Ta interface{}która ma wartość polegającą na tym, Tże ma różne reprezentacje w pamięci, więc nie można jej trywialnie przekonwertować.

Zmienna typu Tjest tylko jej wartością w pamięci. Brak powiązanych informacji o typie (w Go każda zmienna ma jeden typ znany w czasie kompilacji, a nie w czasie wykonywania). Jest on reprezentowany w pamięci w następujący sposób:

  • wartość

Trzymanie interface{}zmiennej typu Tjest reprezentowane w pamięci w ten sposób

  • wskaźnik do pisania T
  • wartość

Wracając do pierwotnego pytania: dlaczego go nie jest domyślnie konwertowany []Tna []interface{}?

Konwersja []T na []interface{}wymagałaby utworzenia nowego wycinka interface {}wartości, co nie jest trywialną operacją, ponieważ układ w pamięci jest zupełnie inny.

Nick Craig-Wood
źródło
10
Jest to pouczające i dobrze napisane (+1), ale nie zajmujesz się drugą częścią jego pytania: „Czy jest lepszy sposób, aby to zrobić ...” (-1).
weberc2
13

Oto oficjalne wyjaśnienie: 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
}
Yandry Pozo
źródło
Dobra informacja, ale to nie jest oficjalne wyjaśnienie. To wiki, na której każdy może edytować.
Inanc Gumus
Jak przekonwertować []interfacena oczekiwany typ, taki jak []map[string]interface{}?
Lewis Chan
8

Spróbuj interface{}zamiast tego. Aby odrzucić jako plasterek, spróbuj

func foo(bar interface{}) {
    s := bar.([]string)
    // ...
}
dskinner
źródło
3
To nasuwa kolejne pytanie: w jaki sposób OP powinien się powtarzać bar, aby zinterpretować go jako „plasterek dowolnego rodzaju”? Zwróć uwagę, że jego trójwarstwowa warstwa tworzy []interface{}, nie []stringlub plasterek innego rodzaju betonu.
kostix 10.10.12
14
-1: Działa to tylko wtedy, gdy bar jest ciągiem [], w takim przypadku możesz również napisać:func foo(bar []string) { /* ... */ }
weberc2
2
użyj przełącznika typu lub użyj odbicia jak w zaakceptowanej odpowiedzi.
dskinner
OP będzie musiał w jakikolwiek sposób określić swój typ w czasie wykonywania. Użycie non-slice interface{}sprawi, że kompilator nie będzie narzekał podczas przekazywania argumentu jak w foo(a). Mógłby użyć przełączania refleksji lub przełączania typów ( golang.org/doc/effective_go.html#type_switch ) w środku foo.
Cassiohg
2

Konwertuj interface{}na dowolny typ.

Składnia:

result := interface.(datatype)

Przykład:

var employee interface{} = []string{"Jhon", "Arya"}
result := employee.([]string)   //result type is []string.
Yala Ramesh
źródło
2
Jesteś tego pewien? Nie sądzę, żeby to było ważne. Zobaczycie: invalid type assertion: potato.([]string) (non-interface type []interface {} on left)
Adriano Tadao
2

Jeśli potrzebujesz więcej skrótów kodu, możesz utworzyć nowy typ pomocnika

type Strings []string

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

następnie

a := []strings{"a", "b", "c", "d"}
sliceIFace := Strings(a).ToInterfaceSlice()
RaditzLawliet
źródło