Фон
Проблема с вашим "упрощенным подходом" заключается в том, что срез (любого типа) имеет тип struct
, состоящий из указателя и двух целых чисел;указатель содержит адрес базового (резервного) массива данных, а целые числа содержат то, что встроенные функции len()
и cap()
возвращают для этого среза.
Другими словами, срез имеет вид представление в массиве.
Тогда в Go нет понятия приведения типа;существуют только преобразования типов, и эти преобразования могут происходить только между типами с одним и тем же базовым представлением 101.
Поскольку срез и массив могут не иметь одно и то же базовое представление (массив буквальнонепрерывный блок памяти размера, достаточного для размещения всех элементов массива), предполагаемое преобразование типа может быть недопустимым.
Возможные решения
Существует два возможных решения.
Самое простое - просто скопировать данные из резервного массива среза во вновь выделенный массив:
var (
src = []byte{1, 2, 3, 4, 5, 6, 7, 8}
dst [8]uint8
)
copy(dst[:], src[:8])
Обратите внимание, что существует существенное несоответствие между срезами типов массива: тип массива кодирует обатип его элементов и его длина (то есть длина является частью типа), тогда как тип среза кодирует только тип его элементов (и может иметь любую длину во время выполнения).
Thisозначает, что вам может потребоваться проверка перед таким копированием, чтобы убедиться, что исходный фрагмент имеет ровно 8элементы, то есть len(src) == len(dst)
.
Этот инвариант может быть реализован с помощью некоторого другого кода, но я думаю, что я заранее предупредил бы вас об этом: если src
имеет менее 8 элементов, то src[:8]
выражение будет паниковать во время выполнения, и если оно содержит больше, то возникает вопрос, является ли копирование только первых 8 из них именно тем, что нужно.
Второй подход (по общему признанию, более сложный) заключается в том, чтобы просто повторноиспользуйте базовый массив слайса:
import "unsafe"
var (
src = []byte{1, 2, 3, 4, 5, 6, 7, 8}
dstPtr *[8]uint8
)
if len(src) != len(*dstPtr) {
panic("boom")
}
dstPtr = (*[8]uint8)(unsafe.Pointer(&src[0]))
Здесь мы просто взяли адрес первого элемента, содержащегося в базовом массиве слайса, и выполнили «грязное» двухфазное преобразование типа, чтобы сделатьполученный указатель будет иметь тип *[8]uint8
, то есть «адрес массива 8 uint8
с».
Обратите внимание на два предостережения:
Полученный указатель теперь указывает на тот же блок памяти, что и исходный фрагмент.Это означает, что теперь возможно изменять эту память как через срез, так и через полученный указатель.
Как только вы решите присвоить данные массива переменной типа [8]uint8
(и передавая его в качестве аргумента параметру функции этого типа), вы будете разыменовывать этот указатель (как при *dstPtr
), и в этот момент данные массива будут скопированы.
Я специально упоминаю об этом, так как часто люди прибегают к таким хакерам, чтобы вытащить резервный массив из среза именно в попытке не скопировать память.
TL; DR
Скопировать данные (после якобы проверки удержания инварианта len(src) == len(dst)
).
Быстрое копирование 8 байтов (на типичном 64-битном процессоре это будетбудет одной MOV
инструкцией (или максимум двумя), и код будет простым.
Прибегайте к взлому из второго решения, только если вам действительно необходимо оптимизировать какой-либо критический горячий путь.В этом случае тщательно прокомментируйте решение и следите за тем, чтобы не случайно разыменовать указатель.
¹ Из этого правила есть единственное, но заметное исключение: []byte
преобразуется в string
и наоборот.