Тип конвертирующих фрагментов интерфейсов

194

Мне интересно , почему Go does't неявно преобразовать []Tв []interface{}когда он будет неявно преобразовать Tв interface{}. Есть ли что-то нетривиальное в этом преобразовании, которое я пропускаю?

Пример:

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

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

go build жалуется

нельзя использовать (строку типа []) как интерфейс типа [] {} в аргументе функции

И если я попытаюсь сделать это явно, то же самое: b := []interface{}(a)жалуется

невозможно преобразовать (строку типа []) в интерфейс типа [] {}

Таким образом, каждый раз, когда мне нужно сделать это преобразование (которое, кажется, очень много), я делал что-то вроде этого:

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

Есть ли лучший способ сделать это, или стандартные функции библиотеки, чтобы помочь с этими преобразованиями? Кажется глупым писать 4 дополнительные строки кода каждый раз, когда я хочу вызвать функцию, которая может принимать список, например, целые числа или строки.

Danny
источник
таким образом, вы определяете «неявное преобразование [] T в [] interface {}» в значении «выделение нового фрагмента и копирование всех элементов поверх». Это не согласуется с тем, что делает «неявное преобразование T в интерфейс {}», что является просто представлением того же значения, что и более общий статический тип; ничего не копируется; и если вы введете его обратно в тип T, вы все равно получите то же самое.
newacct
1
я не думал слишком подробно о том, как это будет реализовано, просто на более высоком уровне было бы удобно иметь возможность легко преобразовать весь список в другой тип в качестве обходного пути для отсутствия универсальных типов.
Дэнни

Ответы:

215

В Go существует общее правило, согласно которому синтаксис не должен скрывать сложные / дорогостоящие операции. Преобразование stringв interface{}делается в O (1) времени. Преобразование []stringв interface{}также делается в O (1) времени , так как кусочек еще одно значение. Однако преобразование a []stringв a - []interface{}это O (n), потому что каждый элемент среза должен быть преобразован в a interface{}.

Единственным исключением из этого правила является преобразование строк. При преобразовании stringв и из a []byteили a []runeGo выполняет O (n), даже если преобразования являются «синтаксисом».

Не существует стандартной библиотечной функции, которая сделает это преобразование за вас. Вы можете сделать один с отражением, но это будет медленнее, чем трехстрочный вариант.

Пример с отражением:

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
}

Ваш лучший вариант - использовать строки кода, которые вы задали в своем вопросе:

b := make([]interface{}, len(a))
for i := range a {
    b[i] = a[i]
}
Стивен Вайнберг
источник
3
это может быть медленнее, но оно будет работать в общем с любым типом среза
newacct
Весь этот ответ относится к mapS слишком кстати.
RickyA
Это также относится и к channels.
Джастин
Спасибо за четкое объяснение, если возможно, вы можете добавить исх. для Converting a []string to an interface{} is also done in O(1) time?
Mayur
56

То, что вам не хватает, - это то, что Tи interface{}которое имеет значение, Tимеет различные представления в памяти, поэтому не может быть легко преобразовано.

Переменная типа T- это просто ее значение в памяти. Информация о связанном типе отсутствует (в Go каждая переменная имеет один тип, известный во время компиляции, а не во время выполнения). В памяти это выглядит так:

  • стоимость

interface{}Держит переменная типа Tпредставлена в памяти , как это

  • указатель на тип T
  • стоимость

Итак, возвращаясь к вашему первоначальному вопросу: почему go не конвертируется []Tв явном виде []interface{}?

Преобразование []Tв []interface{}будет включать создание нового среза interface {}значений, что является нетривиальной операцией, поскольку макет в памяти совершенно другой.

Ник Крейг-Вуд
источник
10
Это информативно и хорошо написано (+1), но вы не обращаетесь к другой части его вопроса: «Есть ли лучший способ сделать это ...» (-1).
weberc2
13

Вот официальное объяснение: 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
}
Яндри позо
источник
Хорошая информация, но это не официальное объяснение. Это вики, где каждый может редактировать.
Inanc Gumus
Как мне перейти []interfaceна тип, который я ожидаю, например []map[string]interface{}?
Льюис Чен,
8

Попробуй interface{}вместо этого. Чтобы отбросить как кусочек, попробуйте

func foo(bar interface{}) {
    s := bar.([]string)
    // ...
}
dskinner
источник
3
Это напрашивается на следующий вопрос: как тогда предполагается, что OP перебирает, barчтобы интерпретировать его как «фрагмент любого типа»? Обратите внимание, что его три лайнера создает, а []interface{}не []stringили кусок другого конкретного типа.
kostix
14
-1: Это работает, только если bar является строкой [], в этом случае вы также можете написать:func foo(bar []string) { /* ... */ }
weberc2
2
используйте переключатель типа или используйте отражение, как в принятом ответе.
dskinner
ОП придется выяснить его тип во время выполнения любым способом. Использование non slice interface{}заставит компилятор не жаловаться при передаче аргумента как в foo(a). Он мог использовать рефлекс или переключение типов ( golang.org/doc/effective_go.html#type_switch ) внутри foo.
Cassiohg
2

Конвертировать interface{}в любой тип.

Синтаксис:

result := interface.(datatype)

Пример:

var employee interface{} = []string{"Jhon", "Arya"}
result := employee.([]string)   //result type is []string.
Яла Рамеш
источник
2
Ты уверен в этом? Я не думаю, что это действительно. Вы увидите это: invalid type assertion: potato.([]string) (non-interface type []interface {} on left)
Адриано Тадао
2

Если вам нужно более короткое замыкание кода, вы можете создать новый тип для помощника

type Strings []string

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

затем

a := []strings{"a", "b", "c", "d"}
sliceIFace := Strings(a).ToInterfaceSlice()
RaditzLawliet
источник