ИМХО, размысшления об указателях и структурах - это тяжёлое наследие С++ с его конструктором копирования и семантикой сдвига. Go устроен принципиально иначе. Это язык со сборщиком мусора, значительно более простой семантикой и, как следствие, продвинутым оптимизатором. Вот правда, оптимизатор в Go творит какую-то запредельную жесть. Вы как-нибудь поглядите в ассемблер бинарника, собранного с оптимизацией и отключенными данными отладки: вообще ничего похожего на исходный код!
К чему это я. К тому, что в Go весьма продвинутый статический анализатор использования объекта (так называемый escape analysis). Если объект используется только для чтения, то его копия создаваться не будет. Это к вопросу о for _, value := range slNew
. Если внутри цикла value
не будет модифицироваться, то сто-пудово компилятор не будет создавать временный объект для хранения value
.
Теперь про "копирование" слайса. Инструкция slNew := sl
не копирует слайс. Эта инструкция создаёт небольшой объект типа SliceHeader
, копирует в эту структуру указатель на собственно память с данными слайса, длину слайса и максимальный размер слайса, и всё. Если вам нужно создать именно дубликат слайса, то есть слайс с теми же данными, но расположенный в другой области памяти, вам нужно использовать встроенную функцию copy
:
slNew := make([]MyStruct, len(sl))
copy(slNew, sl)
Эта пара операций, разумеется, гораздо быстрее в случае слайса указателей, просто в силу того, что указатель занимает всего 8 байт, а структуры могут быть гораздо большего размера. Копирование массивов из тысячи элементов: бенчмарк здесь
BenchmarkSlicePointers-8 432214 2962 ns/op 8192 B/op 1 allocs/op
BenchmarkSliceNoPointers-8 135276 10121 ns/op 32768 B/op 1 allocs/op
Бенчмарк показывает, что копирование указателей в три с половиной раза быстрее, чем копирование массива структур, но это напрямую вытекает из того, что в бенчмарке массив структур в четыре раза больше по размеру.
Кстати, благодаря тому, что при простом присваивании копируется только заголовок слайса, а не сам слайс, то при передаче в функции слайс передаётся фактически по ссылке: https://go.dev/play/p/0XpYmxDpr_w
package main
import "fmt"
func main() {
var slice []int
for i := 0; i < 10; i++ {
slice = append(slice, i)
}
fmt.Println(slice)
modify(slice, 10)
fmt.Println(slice)
}
func modify(slice []int, value int) {
slice[0] = value
}
Результат:
[1 2 3 4 5 6 7 8 9 10]
[10 2 3 4 5 6 7 8 9 10]
ИТОГО
Я всегда пользуюсь слайсами структур и стараюсь не связываться с указателями. Оптимизатор Go умнее меня, и лучше ему не мешать, ИМХО.