[БЕЗ_ЗВУКА] В этом видео мы поговорим про те вещи, которыми, я надеюсь, вы никогда не будете пользоваться. Называется это unsafe. unsafe — это пакет, это инструмент компилятора, он позволяет залезать уже в кишки памяти, достучаться до того, что там лежит глубже. Если вы используете unsafe, вам не гарантируется обратная совместимость между версиями Go, потому что там может что-то внутри меняться. Также unsafe запрещен в некоторых облачных платформах, например, в Google облаке. Unsafe позволяет достучаться прямо до реальной памяти. Это бывает иногда нужным, но я очень надеюсь, вы никогда не будете этим пользоваться. Рассмотрим данный пример. Вот у нас есть int64. Я хочу посмотреть указатель на этот адрес и размер. [БЕЗ_ЗВУКА] Так, запускаем, и вот мне вывелся указатель и размер 8 байт. Это уже не uintptr, не то, что вот обычный &a, а там прямо совсем кишки. Вот. И рассмотрим другой пример, дальше, то есть то, что с этим можно сделать. Рассмотрим функцию Float64bits. Туда мы передадим float64, а вернется нам int. То есть что вообще тут будет происходить-то? Мы возьмем адрес &f, то есть uintptr. И от него возьмем уже Pointer. А дальше мы интерпретируем это уже как память, просто память. А дальше мы интерпретируем как uint64. И давайте посмотрим, как это выглядит. Вот наш float, который мы туда запихнем, 10.1. И выведем его в десятеричной и в других системах. Запустим. Вот вывелся в десятеричной, то есть было у нас 10, стало что-то другое. Вывелся в x и вывелся в бинарном виде, например, можно еще что-то добавить. И значения уже стали совершенно другие. То есть можно посмотреть прямо в Python, где мантисса, где экспонента у целочисленного числа. То есть можно интерпретировать какую-то область памяти как совершенно другое значение. Я надеюсь, вы этого никогда делать не будете. Есть еще один пример. Рассмотрим вот эту структуру Message. Есть flag, есть строка, есть еще один flag. Именно в таком порядке. Я создам экземпляр. Теперь я залезу в кишки этой структуры и посмотрю, сколько в памяти занимает сама эта структура и сколько занимает какое из полей ее данных и какой им Offset желателен, и какой Offset в памяти находится у этой структуры. То есть например, вы можете достучаться, просто сдвинувшись по памяти, и вы сможете достучаться до вот этого поля вот в этой структуре. Конечно, так делать не надо. Вот рассмотрим, Sizeof, сколько оно весит, Align, выравнивание и Offset. Давайте это запустим. Мы уже запустили. Смотрите. Размер этой структуры 32 байта в памяти. Почему 32 байта, откуда они взялись? flag1 — он у нас по Offset 0, то есть его смещение в памяти относительно этих 32 байт — он нулевой. Дальше идет name — это строчка, она занимает 16 байт, потому что внутри строчки там есть ссылка на данные и длина. При этом компилятор нам советует, что выравнивание должно быть 8, но находится оно по Offset 8. Что такое выравнивание? Зачем оно надо? Дело в том, что оперирует компилятор машинными словами. У меня 64-битная операционная система, у меня размер машинного слова 8. Поэтому и занимает bool, булевая переменная, несмотря на то, что Sizeof у нее 1, занимает он 8 байт. То есть Offset находится относительно... То есть моя переменная name занимает второе место. Offset ее относительно flag1. И что мы видим? flag1 занимает один байт, но он занимает целое машинное слово. То есть если я переделаю свою структуру, и я смогу вместить в эти 8 байт еще какие-то значения. Поменяем местами флаги в Message, вот так. Теперь у меня идет две булевых переменных, а потом идет string. Запустим. Смотрите. Была структура 32 байта, стала 24 байта. Почему? Потому что я эффективнее использую память. То есть раньше у меня flag1 занимал по сути 8 байт, и 7 было неиспользуемых. И потом был еще один такой flag2. А теперь я в начало, в эти первые 8 байт, я уместил flag1, flag2, и потом пошло поле name, которое уже строковое. То есть в конце, за счет того, что я передвинул из конца flag2 в начало, я сэкономил аж 8 байт. Если у вас какие-то очень-очень большие структуры, то вот такой перекомпоновкой вы можете выиграть немножко места. Если вы их вдруг выполните миллионы, то, возможно, это будет даже довольно значительно. Есть инструменты, которые позволяют анализировать структуру и предлагают поменять поля местами, чтобы было эффективнее использование памяти. На самом деле вам заморачиваться по этому поводу не стоит. Просто это та вещь, про которую полезно знать для общего развития. Рассмотрим еще один пример. Как вы помните, мы можем конвертировать слайс байт в строку и обратно. При этом, если мы хотим их потом дальше использовать, то создается копия данных. А что делать в случае, если мы не хотим создавать копию данных? Мы хотим как-то поэкономить и оптимизировать нашу программу. В этом случае мы можем воспользоваться пакетом unsafe и, используя указатель, напоминаю, unsafe pointer который, для того чтобы создать строку, которая будет ссылаться на те же самые данные, на которые ссылается слайс байт. Например, вот у меня есть слайс байт, ну неважно какой. Какие-то данные. Теперь я их конвертирую в строку. Что я делаю? Значит, мне приходит слайс байт, теперь я получаю Pointer на эти данные, то есть слайс — это тоже структура. У нее есть, я напоминаю, длина, capacity и ссылка на данные. Теперь я воспринимаю то, что я получил с этой структурой, я теперь воспринимаю как указатель на заголовок слайса, то есть саму ту базовую структуру, которая в слайсе лежит. То есть у него есть, давайте это запустим, [БЕЗ_ЗВУКА] вот этот SliceHeader, у него есть ссылка на данные, длина и capacity. Теперь я могу использовать эти данные, потому что мне вернулся uintptr, то есть указатель на данные у меня уже есть теперь. Я могу составить из этого структуру, которая называется StringHeader, которая состоит только из указателей на данные и длину. И потом вернуть уже эти данные, воспринимая их как строку. То есть, вот я получил мой заголовок слайса, я тут повыводил данные, вот data. Теперь я создаю новую структуру, указываю у нее ссылку на данные туда же, куда ввел слайс байт, длину указываю ту же самую. Теперь я получаю адрес свежесозданной структуры через unsafe.Pointer и воспринимаю данные по этому адресу как строку, которую и возвращаю. Соответственно, вы можете попробовать сделать и обратную операцию, но это чревато проблемами, если вы где-то ошибетесь, потому что эти данные могут либо не собраться в зависимости от того, что вы сделаете дальше, либо не собраться garbage collector'ом. Либо, наоборот, они могут собраться garbage collector'ом, когда они еще вами реально используются. То есть эта оптимизация имеет тоже свою цену. Лучше всего туда не лезть. То есть вам стоит лезть в unsafe тогда, когда вы точно знаете, что вы делаете и зачем вам это надо. Если вы думаете, что вот так нужно делать всегда, просто потому что это быстрее, это не так, и в unsafe лезть не надо. Есть те вещи, которые пробовать не стоит. И также я напоминаю, что, например, в облаке Google вы свое приложение не разместите, потому что unsafe там запрещен. unsafe используется, например, в cgo, про который мы поговорим дальше.