From 6bca0507ff1172d9ddfbd3557752013dc0ef970a Mon Sep 17 00:00:00 2001 From: zed Date: Sat, 11 Nov 2023 00:27:29 +0300 Subject: [PATCH] On branch main modified: src/ch01.md new file: src/ex-ch01-04.zig new file: src/ex-ch01-05.zig --- src/ch01.md | 182 ++++++++++++++++++++++++++++++++++++++++++++- src/ex-ch01-04.zig | 10 +++ src/ex-ch01-05.zig | 6 ++ 3 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 src/ex-ch01-04.zig create mode 100644 src/ex-ch01-05.zig diff --git a/src/ch01.md b/src/ch01.md index 96aff6e..2a912b3 100644 --- a/src/ch01.md +++ b/src/ch01.md @@ -53,7 +53,7 @@ zig run ex-ch01-01.zig и базируется на 2-х вещах * `@import` - это встроенная в компилятор функция -* `pub` - ключевое слово для экспортирования чего-то +* `pub` - ключевое слово для экспортирования различных определений При импортировании модуля нужно указать его имя. Стандартная библиотека Zig доступна по имени `std`. Чтобы импортировать какой-то конкретный файл, нужно использовать @@ -492,3 +492,183 @@ pub fn main() void { "бинарные" данные или строка в кодировке UTF-8. Как бы то ни было, для всех случаев используется один и тот же тип, как для собственно строк, так и для всего прочего. + +Из того, что мы уже знаем про массивы и срезы, мы могли бы заключить, +что `[]const u8` это срез неизменяемого массива байтов, беззнаковых +8-ми битных целых чисел. Но при инициализации строк мы нигде никакие +массивы не использовали, мы просто писали `.name = "Пётр".` Как это работает? + +Длины строковых констант (strings literals, буквальные значения) +известны во время компиляции. Компилятор знает, что строка `"Пётр"` имеет +длину 8 байт (в UTF-8 на каждую букву кириллицы идёт 2 байта), +так что мы можем предположить, что тип этой константы будет `[8]const u8`. +Однако, строковые константы обладают парочкой особых свойств, а именно, +для их хранения в исполнимом файле отведено специальное место, +при этом дубликаты исключаются. Поэтому переменная, через которую +доступна та или иная строковая константа, по идее, должна быть указателем +в это специальное место. Это означает, что тип строки `"Пётр"` это скорее +указатель вида `*const [8]u8`, то есть указатель на неизменяемый массив +длиной 8 байт. + +Но это ещё не всё. Строковые константы заканчиваются бинарным нулём (как в C). +Собственно, так сделано как раз ради "взаимодействия" с кодом, уже +когда-то написанным на языке C. Строка `"Пётр"` в памяти будет выглядеть +как `{0xD0, 0x9F, 0xD1, 0x91, 0xD1, 0x82, 0xD1, 0x80, 0x00}` и тогда, +возможно, вы подумаете, что её тип это `*const [9]u8`, Но это в лучшем +случае будет неоднозначным, а в худшем - даже опасным. Поэтому для представления +строк с признаком конца в виде `\0` (null-terminated strings) в Zig есть +специальный синтаксис - вообщем, строка `"Пётр"` имеет тип `*const [8:0]u8`, +то есть указатель на массив из 8-ми байт с дополнительным нулём на конце. +Тут мы невольно сделали акцент именно на C-подобные строки, однако, +этот синтаксис более общий - [LENGTH:SENTINEL], где `SENTINEL` это специальное значение, +служащее для обозначения конца массива. Вот странный пример с непонятным +потенциальным применением, но тем не менее, он вполне корректен с точки +зрения Zig: + +```zig +const std = @import("std"); + +pub fn main() void { + // массив из 3 булевских значений с false на конце + const a = [3:false]bool{false, true, false}; + + // эта строка более сложная, объясняться не будет + std.debug.print("{any}\n", .{std.mem.asBytes(&a).*}); +} +``` + +Работает, как и задумано (то есть признак конца тоже печатается): + +``` +$ /opt/zig-0.11/zig run src/ex-ch01-04.zig +{ 0, 1, 0, 0 } +``` + +Весьма вероятно, что у Вас остался ещё один вопрос. Если `"Пётр"` это +`*const [8:0]u8`, а поле `name` это `[]const u8`, то как так получается. +что мы свободно присваиваем первое второму? Ответ простой - в этом случае +Zig делает приведение типов (coercion) автоматически. Это означает, +что если функция имеет параметр с типом `[]const u8` или поле структуры +имеет типа `[]const u8`, то при передаче параметра или приваивании значения +полю можно использовать строковые конатанты. Приведение типов в этом +случае не требует больших временных затрат, поскольку строки с нулём +на конце это массивы, длина их известна на этапе компиляции, поэтому +не нужно проходить по всей строке в поисках признака конца. + +Итак, когда мы говорим о строках, то обычно мы имеем ввиду `[]const u8`. +Когда необходимо, мы явно указываем, что строка в стиле C, то есть +с нулём на конце и помним, что она автоматически приводится к `[]const u8`. +Но опять же, мы помним, что `[]const u8` используется также для +произвольных бинарных данных и поэтому в Zig нет специального типа для строк. + +Конечно, в "настоящих" программах большинство строк/массивов неизвестны +во время компиляции - данные могут поступать от пользователя, из сети и т.п. +К этому вопросу мы ещё вернёмся, когда будем рассматривать работу +с оперативной памятью. А сейчас мы пока скажем следующее - для +такого рода данных, которые возникают лишь в процессе работы программы, +память выделяется динамически. При этом наши переменные всё равно будут +иметь тип `[]u8` и указатель в этих срезах будет показывать на динамически +(то есть в процессе выполнения программы) выделенную память. + +## comptime и anytype + +В нашем примере осталась ещё одна совершенно не исследованная строчка: + +```zig +std.debug.print("{s} обладает силой {d}\n", .{user.name, user.power}); +``` +Тут происходит намного больше, чем может показаться на первый взгляд. +За этой с виду простой строчкой скрываются 2 из более мощных +возможностей Zig, о которых нужно иметь представление, даже +если Вы пока ещё не овладели ими в совершенстве. + +Первая из них это выполнение кода во время компиляции, которое +обозначается ключевым словом `comptime`. Это основа для метапрограммирования +в Zig и, как подразумевает само название, речь идёт о выполнении кода +во время компиляции, а не во время исполнения уже откомпилированной прораммы. +На протяжении этой книги мы лишь слегка коснёмся этой темы, +но надо понимать, что эта особенность всегда незримо присутствует. + +Вам уже, наверное, не терпится спросить, а какое отношение к этому +имеет та самая строчка? Давайте посмотрим на сигнатуру функции `print`: + +```zig +// обратите внимание на 'comptime' перед первым аргументом +pub fn print(comptime fmt: []const u8, args: anytype) void { +``` + +Ключевое слово `comptime` перед `fmt` означает, что строка формата +должна быть известна во время компиляции. Причиной для этого является +то, что `print` во время компиляции делает различные проверки. +Что это за проверки? Ну, скажем, Вы поменяли формат на `"it's over {d}\n"`, +но при этом оставили 2 аргумента. Тогда Вы получите ошибку компиляции. +Также делается проверка на соответствие типов: измените формат на +`"{s}'s power is {s}\n"` и Вы тоже получите ошибку про несоответствие +`u64` спецификатору `{s}`. Подобного рода проверки были бы невозможны +во время компиляции, если бы строка формата была бы неизвестна во время +этой самой компиляции, отсюда такое требование для параметра `fmt`. + +Одна из ситуаций, где Вы сразу же столкнётесь с `comtime` это +типы по умолчанию для целочисленных констант и констант с плавающей точкой - +`comptime_int` и `comptime_float`. Например, строчка `var i = 0;` +не является корректной, компилятор в это месте скажет, что +"переменная типа `comptime_int` должна быть `const` или `comptime`". +Любой код, который помечен как `comptime`, может работать с данными, +известными во время компиляции и, что касается целых и вещественных чисел, +такие данные имеют специальные типы, `comptime_int` и `comptime_float`, +соответственно. Однако, вряд ли Вы будете большую часть времени писать +код, предназначенный для выполнения во время компиляции, так что +это не особо полезное умолчание. То, что Вам нужно, это просто явно задать типы, +например + +```zig +var i: usize = 0; +var j: f64 = 0; +``` + +Отметим также, что если бы мы написали `const i = 0` (а не `var i = 0`), +то ошибки бы не было, поскольку вся её (ошибки) суть в том, что +"переменные" типа `comptime_int` _должны_ быть константой. + +В одной из следующих глав мы познакомимся с `comptime` поближе, +когда будем изучать обобщённые структуры данных ("generics"). + +Ещё одна особенная вещь, которая имеется в нашей строчке, это +вот это странное `{user.name, user.power}`, которое, как следует +из сигнатуры `print`, передаётся как `anytype`. Этот тип не следует +путать с чем-то вроде `Object` из Java или `any` из Go. Здесь +дело обстоит так - во время компиляции Zig сгенерирует специализированные +варинаты `print` для всех типов, которые Вы передавали `print` в Вашей +программе. + +Тут возникает вопрос - а _что_, собственно, мы передаём в функцию `print`? +Мы уже видели подобную запись, когда немного говорили об автоматическом +выведении типов. Здесь ситуация похожая, но тут такая запись создаёт +анонимную структуру. Запустите вот такой код: + +```zig +const std = @import("std"); +pub fn main() void { + std.debug.print("{any}\n", .{@TypeOf(.{.year = 2023, .month = 8})}); +} +``` + +Он напечатает + +``` +struct{comptime year: comptime_int = 2023, comptime month: comptime_int = 8} +``` + +Тут мы дали полям анонимной структуры имена (`year` и `month`). +В изначальном варианте мы этого не делали, просто указывали значения. +В таких случаях имена полей генерируются автоматически, они будут +такие - `"0"`, `"1"`, `"2"` и т.д. Функция `print` ожидает структуру +с такими полями и использует позиции спецификаторов в строке формата +для получения соответствующего аргумента. + +В Zig нет перегрузки функций ("function overloading"), а также нет +функций с переменным числом аргументов ("variadic functions"). +Вместо этого компилятор Zig умеет создавать специализированные варианты +функций, основываясь на передаваемых в "универсальный" вариант типах, +как выведенных, так и созданных самим компилятором. + diff --git a/src/ex-ch01-04.zig b/src/ex-ch01-04.zig new file mode 100644 index 0000000..8a05be6 --- /dev/null +++ b/src/ex-ch01-04.zig @@ -0,0 +1,10 @@ + +const std = @import("std"); + +pub fn main() void { + // an array of 3 booleans with false as the sentinel value + const a = [3:false]bool{false, true, false}; + + // This line is more advanced, and is not going to get explained! + std.debug.print("{any}\n", .{std.mem.asBytes(&a).*}); +} diff --git a/src/ex-ch01-05.zig b/src/ex-ch01-05.zig new file mode 100644 index 0000000..762807a --- /dev/null +++ b/src/ex-ch01-05.zig @@ -0,0 +1,6 @@ + +const std = @import("std"); + +pub fn main() void { + std.debug.print("{any}\n", .{@TypeOf(.{.year = 2023, .month = 8})}); +}