Введіть перетворювальні фрагменти інтерфейсів


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 скаржиться

не може використовувати рядок (type []) як інтерфейс type [] {} в аргументі функції

І якщо я спробую це зробити явно, те саме: b := []interface{}(a)скаржиться

не може конвертувати (type [] рядок) у тип [] інтерфейсу {}

Тому щоразу, коли мені потрібно робити це перетворення (яке, здається, виникає багато), я робив щось подібне:

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

Чи є кращий спосіб зробити це, або стандартні функції бібліотеки, щоб допомогти у цих перетвореннях? Дуже нерозумно писати 4 зайвих рядки коду кожного разу, коли я хочу викликати функцію, яка може взяти список, наприклад, ints або string.


тому ви визначаєте "неявно перетворити [] T в [] інтерфейс {}", щоб означати "виділення нового фрагмента та копіювання всіх елементів на". Це суперечить тому, що "неявно перетворює T в інтерфейс {}", що є лише видом того ж значення, що і більш загальний статичний тип; нічого не копіюється; і якщо ви введете-затвердите його назад до типу T, ви все одно отримаєте те саме.
newacct

1
не задумувався надто детально про те, як це було б реалізовано, тільки що на більш високому рівні було б зручно легко перетворити цілий список на інший тип як вирішення для відсутності загальних типів.
danny

Відповіді:


215

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

Єдиним винятком із цього правила є перетворення рядків. При перетворенні a stringв і з a []byteабо a []rune, Go does 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 занадто btw.
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
  • значення

Так що, повертаючись до початкового питання: чому йдуть does't неявно перетворити []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)
    // ...
}

3
Це вимагає наступного питання: як тоді ОП повинна повторюватися bar, щоб інтерпретувати її як "фрагмент будь-якого типу"? Зауважте, що його трилінійний наріз створює []interface{}не, []stringабо шматочок іншого типу бетону.
kostix

14
-1: Це працює лише якщо рядок є рядком [], тоді ви можете також написати:func foo(bar []string) { /* ... */ }
weberc2,

2
використовуйте перемикач типу або використовуйте відображення, як у прийнятій відповіді.
dskinner

ОП доведеться будь-яким чином розібратися з його типом. Використання "non slice interface{}" змусить компілятор не скаржитися при передачі аргументу як в foo(a). Він може або використовувати перемикання або тип комутації ( golang.org/doc/effective_go.html#type_switch ) всередині foo.
Кассіог

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()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.