Язык Go — хинты. 03 — углубленные срезы

Уж извините, лучше название не придумалось. 🙂

Допустим, у нас есть срез со строками. По какой-то причине нам нужно взять его часть и использовать где-то ещё. Допустим, при использовании нам требуется добавить новый элемент. Как новичёк и не до конца понимающий работу срезов в Go, я бы сделал так:

И ожидал бы увидеть

Вместо этого мы наблюдаем в x

В связи с этим два вопроса:

  1. какого?..
  2. что делать?

Давайте разбираться. 😉

Чтобы понять, почему так происходит, добавим в пример информацию о размере и ёмкости до append.

То есть, «новый» срез имеет ёмкость больше, чем его длина. Его ёмкость равна cap(x) — firstYIndexInX. firstYIndexInX — это индекс элемента, с которого мы брали срез для y.

Объясняется такое поведение тем, что новый срез не является независимой копией элементов из старого. Это тот же срез в памяти. Просто у него указатель на начало данных другой и соответственно уменьшенная ёмкость.

Не спрашивайте меня, почему создатели Go ёмкость нового среза посчитали излишним делать равной длине этого нового среза. Я не знаю. Но жить с этим придётся.

Как же теперь сделать так, чтобы новый срез не портил данные в старом при добавлении элемента? Есть разные способы. Наиболее правильный — создать make‘ом новый срез и скопировать в него нужные элементы. Так точно срезы не будут мешать друг другу. Но в этом подходе есть и минус — гарантированное выделение памяти, даже если нам не потребуется добавлять элементы.

Если мы знаем, что старые элементы мы менять не будем (либо нам это не критично) и потому данные в старом срезе мы этим не испортим, но вот добавление нового элемента в новый срез вполне возможно, нам помогут reflect + unsafe:

One comment

Leave a Reply

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