Tipo de conversão de fatias de interfaces


194

Estou curioso para saber por que o Go não se converte implicitamente []Tem []interface{}quando implicitamente se converte Tem interface{}. Há algo não trivial nessa conversão que estou perdendo?

Exemplo:

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

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

go build reclama

não pode usar um (tipo [] string) como tipo [] interface {} no argumento da função

E se eu tentar explicitamente, a mesma coisa: b := []interface{}(a)reclama

não é possível converter uma (tipo [] string) para digitar [] interface {}

Então, toda vez que preciso fazer essa conversão (o que parece surgir bastante), tenho feito algo assim:

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

Existe uma maneira melhor de fazer isso, ou funções de biblioteca padrão para ajudar com essas conversões? Parece meio bobo escrever 4 linhas de código extras toda vez que eu quiser chamar uma função que pode ter uma lista de, por exemplo, ints ou strings.


então você define "converte implicitamente [] T em [] interface {}" para "alocar uma nova fatia e copiar todos os elementos". Isso é inconsistente com o que "converte implicitamente T em interface {}" faz, que é apenas uma visão do mesmo valor que um tipo estático mais genérico; nada é copiado; e se você digitar de volta para o tipo T, você ainda receberá a mesma coisa.
Newacct

1
não estava pensando em muitos detalhes sobre como seria implementado, apenas que, em um nível superior, seria conveniente poder converter facilmente uma lista inteira em outro tipo como uma solução alternativa por não ter tipos genéricos.
Danny

Respostas:


215

No Go, existe uma regra geral de que a sintaxe não deve ocultar operações complexas / caras. A conversão de um stringpara um interface{}é feita no tempo O (1). A conversão de um []stringpara um interface{}também é feita no tempo O (1), pois uma fatia ainda é um valor. No entanto, converter a []stringem um []interface{}é tempo O (n) porque cada elemento da fatia deve ser convertido em um interface{}.

A única exceção a esta regra é a conversão de cadeias. Ao converter um stringpara e de um []byteou a []rune, Go funciona O (n), mesmo que as conversões sejam "sintaxe".

Não há função de biblioteca padrão que faça essa conversão para você. Você poderia fazer um com refletir, mas seria mais lento que a opção de três linhas.

Exemplo com reflexão:

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
}

Sua melhor opção é apenas usar as linhas de código que você forneceu na sua pergunta:

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

3
pode ser mais lento, mas ele iria trabalhar genericamente com qualquer tipo de fatia
newacct

Toda essa resposta se aplica a maps também.
RickyA

Isso também se aplica channelstambém.
Justin Ohms

Obrigado pela explicação clara, se possível, você pode adicionar ref. para Converting a []string to an interface{} is also done in O(1) time?
Mayur

56

A coisa que está faltando é que Te interface{}que detém um valor de Tter diferentes representações na memória de modo não pode ser trivialmente convertido.

Uma variável do tipo Té apenas o seu valor na memória. Não há informações de tipo associadas (no Go, cada variável possui um único tipo conhecido no tempo de compilação e não no tempo de execução). É representado na memória assim:

  • valor

Uma interface{}retenção de uma variável do tipo Té representada na memória como esta

  • ponteiro para digitar T
  • valor

Voltando à sua pergunta original: por que ir não é convertido implicitamente []Tem []interface{}?

A conversão []Tpara []interface{}envolveria a criação de uma nova fatia de interface {}valores, que é uma operação não trivial, pois o layout da memória é completamente diferente.


10
Isso é informativo e bem escrito (+1), mas você não está abordando a outra parte da pergunta: "Existe uma maneira melhor de fazer isso ..." (-1).
Weberc2 18/09/2014


8

Tente em interface{}vez disso. Para converter de volta como fatia, tente

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

3
Isso implora pela próxima pergunta: como o OP deve iterar barpara interpretá-lo como "uma fatia de qualquer tipo"? Observe que o revestimento de três camadas cria um []interface{}, não []stringou uma fatia de outro tipo de concreto.
Kostix 10/10/12

13
-1: Isso só funciona se bar for [], nesse caso, você também pode escrever:func foo(bar []string) { /* ... */ }
weberc2

2
use uma chave de tipo ou use reflexão como na resposta aceita.
dskinner

O OP terá que descobrir seu tipo no tempo de execução de qualquer maneira. O uso de não fatia interface{}fará com que o compilador não se queixe ao passar um argumento como em foo(a). Ele poderia usar refletir ou digitar switching ( golang.org/doc/effective_go.html#type_switch ) dentro foo.
Cassiohg 10/10/19

2

Converta interface{}em qualquer tipo.

Sintaxe:

result := interface.(datatype)

Exemplo:

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

2
Voce tem certeza disso? Eu não acho que isso seja válido. Você vai vê-lo: invalid type assertion: potato.([]string) (non-interface type []interface {} on left)
Adriano Tadao

2

Caso precise de um código mais curto, você pode criar um novo tipo para helper

type Strings []string

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

então

a := []strings{"a", "b", "c", "d"}
sliceIFace := Strings(a).ToInterfaceSlice()
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.