Golang slice append gotchas and Common Mistakes
Срезы в Go — очень полезная абстракция массивов Go
Срезы в Go — очень удобный способ работы с динамическими массивами. Однако они не всегда ведут себя так, как можно было бы ожидать.
TL; DR: вот ссылка на вопрос SO, описывающий проблему и решение .
Я предполагаю, что если вы читаете это, вы уже каким-то образом знакомы с Go, поэтому я не буду его никак представлять и сосредоточусь только на slice.
Срезы в Go — очень полезная абстракция массивов Go. И слайсы, и массивы имеют типизированное значение, но массивы также имеют определенную статическую длину, поэтому они обычно не так полезны, так как к ним нельзя добавлять, вырезать и вообще — манипулировать ими по-дружески. То, чего не хватает массивам (по дизайну), обеспечивается срезами.
Одним из наиболее часто используемых методов является встроенная функция добавления :
package main
import "fmt"
func main() {
// create new slice with few elements:
s := []int{1,2,3}
fmt.Printf("slice content: %v\n", s)
// append new element to slice:
s = append(s, 4)
fmt.Printf("slice after append: %v\n", s)
}
В качестве первого аргумента он принимает срез, к которому мы хотим добавить, а в качестве второго — элемент, который мы хотим добавить. Он не изменяет срез, указанный в качестве первого аргумента. Вместо этого он возвращает новый фрагмент.
Выглядит легко, правда?
Но есть один нюанс:
package main
import "fmt"
func create(iterations int) []int {
a := make([]int, 0)
for i := 0; i < iterations; i++ {
a = append(a, i)
}
return a
}
func main() {
sliceFromLoop()
sliceFromLiteral()
}
func sliceFromLoop() {
fmt.Printf("** NOT working as expected: **\n\n")
i := create(11)
fmt.Println("initial slice: ", i)
j := append(i, 100)
g := append(i, 101)
h := append(i, 102)
fmt.Printf("i: %v\nj: %v\ng: %v\nh:%v\n", i, j, g, h)
}
func sliceFromLiteral() {
fmt.Printf("\n\n** working as expected: **\n")
i := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Println("initial slice: ", i)
j := append(i, 100)
g := append(i, 101)
h := append(i, 102)
fmt.Printf("i: %v\nj: %v\ng: %v\nh:%v\n", i, j, g, h)
}
Вы можете запустить этот пример здесь https://play.golang.org/p/G3mmXSNEdY и увидеть, что sliceFromLoop будет печатать всегда последнее добавленное к нему значение!
Почему все предыдущие новые фрагменты, возвращаемые функцией добавления, изменяются последним добавлением?
Поскольку механизм добавления Go постоянно манипулирует базовым массивом, и все новые срезы и их значения также основаны на этом массиве.
Это означает, что создание новых переменных среза на основе механики добавления может привести к очень трудно обнаруживаемым ошибкам и проблемам.
Какое решение?
- используйте append только для добавления нового значения к данному фрагменту, а не для создания нового:
someSlice = добавить (someSlice, новыйЭлемент)
- если вы хотите создать новый слайс на основе старого с некоторым добавленным значением, всегда сначала копируйте его (пример функции кредита идет к пользователю SO ):
func copyAndAppend(i []int, vals ...int) []int {
j := make([]int, len(i), len(i)+len(vals))
copy(j, i)
return append (j, vals...)
}
или просто создать неглубокую копию старого фрагмента:
newSlice := append(T(nil), oldSlice...)
Это не очень часто используемый шаблон. Я обнаружил эту проблему после нескольких лет написания производственного кода на Go, и, вероятно, поэтому она еще более запутана и разочаровывает.
- 2 views
- 0 Comment