Почему изменяется значение слайса?
Возник вопрос, связанный с изменением слайса при вызове метода структуры. В Go передается всё по значению, поэтому в методе должна быть копия слайса структуры, поэтому при любом изменении слайса внутри функции, как я понимаю, не должно быть никаких изменений для слайса самой структуры, так как работаем с копией. Однако в данном примере кода вместо вывода того же слайса получается следующее:
{[123 456 789]}
{[123 789 789]}
При изменении элемента, который как будто удаляется из слайса вывод также изменяется. Если выбрать 123, то будет следующий:
{[123 456 789]}
{[456 789 789]}
А при 789, такой:
{[123 456 789]}
{[123 456 789]}
Пример кода:
package main
import (
"fmt"
"slices"
)
type T struct {
slice []string
}
type ArrayPointer struct {
slice []string
}
func (t T) delete(elem string) {
index := slices.Index(t.slice, elem)
t.slice = slices.Delete(t.slice, index, index+1)
}
func main() {
t := T{[]string{"123", "456", "789"}}
fmt.Println(t)
t.delete("456")
fmt.Println(t)
}
Основной вопрос — почему вообще изменяется слайс и как связан найденный индекс элемента с выводом нового значения слайса? Полагаю, что изменяться слайс может из-за того, что он внутри также содержит указатель на память с элементами, но так до конца этот момент и не смог осознать.
Ответы (1 шт):
Коротко методы, которые изменяют состояние объекта, нужно определять для указателя на тип: func (t *T) delete(elem string). Такие методы оперируют непосредственно с объектом, для которого вызваны. Методы func (t T) оперируют с копией объекта.
Пруф: https://go.dev/play/p/Wau88dFOsIN
{[123 456 789]}
{[123 789]}
Длинно
Слайс - это структура данных из четырёх элементов:
- дескриптор типа элементов слайса,
- указатель на память, где лежат элементы слайса,
- число элементов в слайсе,
- максимальное число элементов, которые может вместить слайс.
Ваша функция T.delete принимает аргумент типа T по значению, то есть создаётся копия t, которая передаётся в функцию.
Внутри функции delete из слайса удаляется элемент, то есть происходит вот что:
- элементы от 0 до
index-1остаются на своих местах, - элементы от
index+1до конца слайса копируются на одно место вперёд, - число элементов в
t.sliceуменьшается на единицу.
Давайте рассмотрим первый случай. После удаления элемента "456" слайс t.slice в функции T.delete имеет вид []string{"123", "789"}.
Новая память не аллоцируется, так как слайс не вырастает за пределы отведённой памяти. Это означает, что третий элемент исходного слайса остался в памяти. Когда функция T.delete возвращает управление, копия t уничтожается. Тот t, который в main, остаётся без изменний - его t.slice указывает на ту же область памяти, длина осталась той же - три элемента. Но функция T.delete изменила содержимое памяти, скопировав элемент "789" на место элемента "456". Но сам элемент "789", стоявший на третьем месте в исходном слайсе, остался на своём месте. Его никто не удалял. Поэтому он остался виден в слайсе main::t.slice
Аналогично во втором случае. При удалении элемента "123" элементы "456" и "789" были скопированы на первую и вторую позицию в памяти, но третий элемент остался в памяти, и остался виден в main::t.slice, так как в этом слайсе по-прежнему три элемента.
В третьем случае ничто никуда не копировали, в T.delete::t.slice уменьшили число элементов на 1. Но так как это копия слайса из main::t, то оригинал не изменился, и элемент "789" остался виден в main.
Поменяйте метод на func (t *T) delete(elem string). Тогда slices.Delete будет оперировать непосредственно слайсом того объекта, для которого был вызван метод delete, а не с его копией.