Share This
Связаться со мной
Крути в низ
Categories
//Golang slice append gotchas and Common Mistakes

Golang slice append gotchas and Common Mistakes

04.01.2023Category : Golang

Срезы в 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

Leave a Reply

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.

Связаться со мной
Close