diff --git a/src/ch01.md b/src/ch01.md index 451f0a9..1a54345 100644 --- a/src/ch01.md +++ b/src/ch01.md @@ -96,10 +96,10 @@ const User = user.User; const MAX_POWER = user.MAX_POWER; ``` -Скорее всего, на данный момент у Вас больше вопросов, чем ответов. Что +Скорее всего, на данный момент у вас больше вопросов, чем ответов. Что такое `user` в приведенном отрывке? Хотя мы ещё не знакомы с ключевым словом `var`, но тем не менее можно спросить, а что, если вместо `const` -использовать `var`? Или. возможно, у Вас возник вопрос по поводу +использовать `var`? Или. возможно, у вас возник вопрос по поводу использования сторонних библиотек. Это всё хорошие вопросы, но чтобы ответить на них, нам нужно глубже изучить Zig, а пока нам придётся обойтись тем, что мы уже знаем, а именно: @@ -168,10 +168,10 @@ fn add(a: i64, b: i64) i64 { Далее отметим тип `i64`. Вот некоторые другие целочисленные/вещественные типы: `u8`, `i8`, `u16`, `i16`, `u32`, `i32`, `u47`, `i47`, `u64`, `i64`, `f32` и `f64`. Наличие в списке `u47` и `i47` это вовсе не -проверка того, что Вы ещё не спите - Zig умеет работать с целыми числами -произвольного размера (в битах). Возможно, Вы не будете использовать +проверка того, что вы ещё не спите - Zig умеет работать с целыми числами +произвольного размера (в битах). Возможно, вы не будете использовать такие типы (`u1`, `s9` и т.п.), однако, иногда они удобны или даже -необходимы. Тип, который Вы точно будете часто использовать, это `usize` +необходимы. Тип, который вы точно будете часто использовать, это `usize` - это целочисленный тип размером с указатель (для данной платформы) и этот тип представляет длину/размер чего-либо. @@ -289,9 +289,9 @@ pub const User = struct { Использование именно такого имени (`init`) является просто соглашением, то есть в каких-то случаях более уместными могут показаться и другие -имена, например, `open`. Если Вы не программист на C/C++, то такой +имена, например, `open`. Если вы не программист на C/C++, то такой синтаксис инициализации полей (с точкой, `.name = name`) может показаться -Вам слегка странным, но со временем Вы к этому привыкнете. +Вам слегка странным, но со временем вы к этому привыкнете. Когда мы создавали пользователя `Пётр`, мы объявили экземпляр `user` как константу (`const`): @@ -305,7 +305,7 @@ const user = User{ Это означает, что мы не можем модифицировать эту "переменную". Чтобы иметь возможность модификации, нужно объявить экземпляр при помощи -ключевого слова `var`, а не `const`. Возможно, Вы так же заметили, что +ключевого слова `var`, а не `const`. Возможно, вы так же заметили, что тип переменной `user` выводится из того, что в неё присваивается. Мы могли бы написать всё явно, то есть вот так: @@ -349,7 +349,7 @@ pub fn init(name: []const u8, power: u64) User { Мы могли бы обойти молчанием последнюю нашего примера (`name: []const u8`), но, поскольку пример содержит две строковые константы (`"Пётр"` и -`"{s} обладает силой {d}\n"`), то наверняка Вы любопытствуете по поводу +`"{s} обладает силой {d}\n"`), то наверняка вы любопытствуете по поводу строк в Zig. Чтобы лучше понимать строки, нужно сначала ознакомиться с массивами и срезами. @@ -374,7 +374,7 @@ const c = [_]i32{1, 2, 3, 4, 5}; С другой стороны, срез - это пара указатель плюс длина, при этом длина становится известной лишь при исполнении программы. Мы пройдёмся по -указателям далее, а пока Вы можете представлять себе срез как некое +указателям далее, а пока вы можете представлять себе срез как некое "окно" в массиве. Однако, не всё так просто. Рассмотрим следующий отрывок кода: @@ -445,7 +445,7 @@ src/ex-ch01-03.zig:8:6: error: cannot assign to constant ~^~~ ``` -Наверняка для исправления ситуации Вам тут хочется поменять `const` на +Наверняка для исправления ситуации вам тут хочется поменять `const` на `var` при объявлении `b`: ```zig @@ -474,7 +474,7 @@ pub fn main() void { ``` Это работает, поскольку теперь тип среза `b` это `[]i32`, а не `[]const -i32`, как было до этого. Тут Вы справедливо возразите - но `b`-то у нас +i32`, как было до этого. Тут вы справедливо возразите - но `b`-то у нас всё равно `const`, как же тогда это может работать?!? Но всё просто - неизменяемость `b` относится к самому `b` (то есть к той самой паре указатель+длина), но не к данным, на которые он показывает, а показывает @@ -565,7 +565,7 @@ $ /opt/zig-0.11/zig run src/ex-ch01-04.zig { 0, 1, 0, 0 } ``` -Весьма вероятно, что у Вас остался ещё один вопрос. Если `"Пётр"` это +Весьма вероятно, что у вас остался ещё один вопрос. Если `"Пётр"` это `*const [8:0]u8`, а поле `name` это `[]const u8`, то как так получается. что мы свободно присваиваем первое второму? Ответ простой - в этом случае Zig делает приведение типов (coercion) автоматически. Это означает, что @@ -602,7 +602,7 @@ std.debug.print("{s} обладает силой {d}\n", .{user.name, user.power Тут происходит намного больше, чем может показаться на первый взгляд. За этой с виду простой строчкой скрываются две мощных возможности Zig, о -которых нужно иметь представление, даже если Вы пока ещё не овладели ими +которых нужно иметь представление, даже если вы пока ещё не овладели ими в совершенстве. Первая из них это выполнение кода во время компиляции, которое @@ -624,15 +624,15 @@ pub fn print(comptime fmt: []const u8, args: anytype) void { Ключевое слово `comptime` перед `fmt` означает, что строка формата должна быть известна во время компиляции. Причиной для этого является то, что `print` во время компиляции делает различные проверки. Что это за -проверки? Ну, скажем, Вы поменяли формат на `"it's over {d}\n"`, но при -этом оставили 2 аргумента. Тогда Вы получите ошибку компиляции. Также +проверки? Ну, скажем, вы поменяли формат на `"it's over {d}\n"`, но при +этом оставили 2 аргумента. Тогда вы получите ошибку компиляции. Также делается проверка на соответствие типов: измените формат на `"{s}'s power -is {s}\n"` и Вы тоже получите ошибку про несоответствие `u64` +is {s}\n"` и вы тоже получите ошибку про несоответствие `u64` спецификатору `{s}`. Подобного рода проверки были бы невозможны во время компиляции, если бы строка формата была бы неизвестна во время этой самой компиляции, отсюда такое требование для параметра `fmt`. -Одна из ситуаций, где Вы сразу же столкнётесь с `comptime` это типы по +Одна из ситуаций, где вы сразу же столкнётесь с `comptime` это типы по умолчанию для целочисленных констант и констант с плавающей точкой - `comptime_int` и `comptime_float`. Например, строчка `var i = 0;` не является корректной, компилятор в это месте скажет, что "переменная" типа @@ -640,9 +640,9 @@ is {s}\n"` и Вы тоже получите ошибку про несоотв помечен как `comptime`, должен работать с данными, известными во время компиляции и, что касается целых и вещественных чисел, такие данные имеют специальные типы, `comptime_int` и `comptime_float`, соответственно. -Однако, вряд ли Вы будете большую часть времени писать код, +Однако, вряд ли вы будете большую часть времени писать код, предназначенный для выполнения во время компиляции, так что это не особо -полезное умолчание. То, что Вам нужно, это просто явно задать типы, +полезное умолчание. То, что вам нужно, это просто явно задать типы, например ```zig @@ -662,7 +662,7 @@ var j: f64 = 0; `print`, передаётся как `anytype`. Этот тип не следует путать с чем-то вроде `Object` из Java или `any` из Go. Здесь дело обстоит так - во время компиляции Zig сгенерирует специализированные варианты `print` для всех -типов, которые Вы передавали `print` в Вашей программе. +типов, которые вы передавали `print` в вашей программе. Тут возникает вопрос - а _что_, собственно, мы передаём в функцию `print`? Мы уже видели подобную запись, когда немного говорили об diff --git a/src/ch02.md b/src/ch02.md index 0b25f20..8bc5860 100644 --- a/src/ch02.md +++ b/src/ch02.md @@ -8,13 +8,13 @@ ## Управление потоком выполнения -Операторы управления потоком исполнения в Zig, безусловно, покажутся Вам +Операторы управления потоком исполнения в Zig, безусловно, покажутся вам знакомыми, однако, имеется ряд моментов, касающихся их взаимодействия с некоторыми другими аспектами языка, которые нам пока лишь предстоит изучить. Мы начнём с краткого обзора и будем возвращаться по мере того, как нам будут встречаться особенности в поведении инструкций управления. -Далее Вы заметите, что для логических операторов "И" и "ИЛИ" в Zig +Далее вы заметите, что для логических операторов "И" и "ИЛИ" в Zig используются обозначения `and` и `or`, а не `&&` и `||`. Как и во многих других языках, эти операторы влияют на ход исполнения программы, поскольку при компиляции выражений, содержащих эти операторы, применяется @@ -309,9 +309,9 @@ Stage.confirmed or self == Stage.err;`, но, как правило, тип оп То, что в операторе `switch` нужно указывать все возможные варианты (ну, если нет оборота `else`), просто замечательно сочетается с перечислениями: если мы что-то забыли, компилятор нам подскажет. И будьте осторожны, -если у Вас в `switch` есть оборот `else` - если Вы вдруг добавите новые +если у вас в `switch` есть оборот `else` - если вы вдруг добавите новые элементы в перечисление, то они все попадут в этот `else` и, скорей -всего, программа не будет работать так, как Вы задумывали. +всего, программа не будет работать так, как вы задумывали. ## Объединения (union) @@ -578,7 +578,7 @@ pub fn main() !void { вполне эквивалентны. Например, ссылки на функции с неявным набором ошибок требуют специального типа, `anyerror`. Разработчики библиотек, возможно, предпочитают быть более явными, так код становится самодокументирующимся. -Так или иначе, оба подхода имеют право на существование и Вы можете +Так или иначе, оба подхода имеют право на существование и вы можете использовать оба по своему усмотрению. Реальная ценность объединений с ошибками проявляется при обработке @@ -634,7 +634,7 @@ try action(req, res); ``` Это полезно в частности потому, что **ошибки должны быть обработаны**. По -большей части Вы будете использовать для этого `catch` или `try`, но +большей части вы будете использовать для этого `catch` или `try`, но объединения с ошибками также поддерживаются операторами `if` и `while`, вполне аналогично тому, как они работают с опциональными значениями. В случае `while`, если условие сразу же даёт ошибку, исполняется оборот diff --git a/src/ch03.md b/src/ch03.md index fb33989..cf32098 100644 --- a/src/ch03.md +++ b/src/ch03.md @@ -34,7 +34,7 @@ fn add(a: i64, b: i64) i64 { Первая ошибка возникла потому, что локальная константа `sum` никак после присваивания ей значения не используется. Вторая ошибка связана с тем обстоятельством, что `b` (второй аргумент функции `add`) тоже внутри -функции никак не используется. Однако, у Вас могут быть вполне +функции никак не используется. Однако, у вас могут быть вполне уважительные причины реально иметь в своём коде неиспользованные локальные переменные или параметры функций, поэтому в Zig имеется возможность игнорировать ненужные значения путём присваивания их символу @@ -88,10 +88,10 @@ fn read(stream: std.net.Stream) ![]const u8 { ## Соглашения об именовании -За исключением правил, диктуемых компилятором, Вы, разумеется, вольны -использовать такие соглашения об именах, какие Вам больше нравятся. +За исключением правил, диктуемых компилятором, вы, разумеется, вольны +использовать такие соглашения об именах, какие вам больше нравятся. Однако, совершенно не вредно понимать собственные соглашения Zig, -поскольку стандартная библиотека, которой Вы, безусловно, будете +поскольку стандартная библиотека, которой вы, безусловно, будете пользоваться, придерживается этих соглашений. Отступы в Zig - это 4 пробела. Как правило, текстовые редакторы можно diff --git a/src/ch04.md b/src/ch04.md index 612b468..f4cf182 100644 --- a/src/ch04.md +++ b/src/ch04.md @@ -7,9 +7,9 @@ Мы начнём изучать указатели, это важная тема как сама по себе, но также и для того, чтобы начать приучать себя смотреть на программы с точки зрения -использования памяти компьютера. Если Вы свободно обращаетесь с +использования памяти компьютера. Если вы свободно обращаетесь с указателями, динамическим выделением/освобождением памяти, понимаете, что -такое "висячий" указатель (dangling pointer), тогда Вы можете пропустить +такое "висячий" указатель (dangling pointer), тогда вы можете пропустить пару глав и перейти к главе 6, там есть некоторые особенности, специфичные для Zig. @@ -118,7 +118,7 @@ user -> ------------ (id: 1043368d0) размер поля `id` равен 8-ми байтам, поле `power` имеет адрес, равный адресу начала структуры плюс 8. -Чтобы Вы могли сами в этом убедиться, мы сейчас введём оператор взятия +Чтобы вы могли сами в этом убедиться, мы сейчас введём оператор взятия адреса, он обозначается символом `&`. Как подразумевает название этого оператора, он возвращает адрес переменной (а также может возвращать адрес функции, то есть адрес, где начинается код функции). Не меняя @@ -143,8 +143,8 @@ i32@7ffdb6cad4e0 ``` -В зависимости от Вашей аппаратной платформы, а также от ряда других -факторов, Вы можете увидеть не конкретно такие числа, но это не важно. +В зависимости от вашей аппаратной платформы, а также от ряда других +факторов, вы можете увидеть не конкретно такие числа, но это не важно. Важно то, что адреса `user` и его первого поля `user.id` одинаковы, а адрес 2-го поля ровно на 8 байт больше. @@ -193,7 +193,7 @@ fn levelUp(user: User) void { } ``` -Если Вы это запустите, то увидите совершенно разные адреса: +Если вы это запустите, то увидите совершенно разные адреса: ``` $ /opt/zig-0.11/zig run src/ex-ch04-05.zig @@ -308,7 +308,7 @@ pub const User = struct { соблюдались бы намерения разработчика. В некоторой степени это возможно как раз благодаря тому, что параметры функций являются константами. -Возможно, ВЫ задались вопросом - а каким образом передача по ссылке может +Возможно, вы задались вопросом - а каким образом передача по ссылке может быть медленнее, чем по значению, даже для маленьких структур? Более ясно мы поймём это далее, но суть в том, что обращение к полям (`user.power`, например) через указатель добавляет некоторые накладные расходы и поэтому @@ -520,14 +520,14 @@ pub const User = struct { В некоторых языках всё это реализуется по другому, но многие языки работают именно так (ну, или близко к этому). Хотя всё это может показаться понятным лишь посвящённым, тем не менее, это является основой -для повседневного программирования. Хорошие новости в том, что Вы можете +для повседневного программирования. Хорошие новости в том, что вы можете освоить эту "эзотерику" используя простые примеры и отрывки кода - по мере того, как сложность остальных частей системы растёт, основы остаются неизменными и не становятся более сложными. ## Рекурсивные структуры -Иногда Вам могут потребоваться, чтобы структура была рекурсивной. +Иногда вам могут потребоваться, чтобы структура была рекурсивной. Давайте добавим в структуру `User` необязательное поле `manager`. Мы также создадим 2-х пользователей и назначим одного из них менеджером другого: @@ -571,7 +571,7 @@ pub const User = struct { имело вполне определённый размер, по 8 байт на указатель и на длину, всего 16 байт. -Возможно, Вы подумаете, что это будет проблемой с любым необязательным +Возможно, вы подумаете, что это будет проблемой с любым необязательным полем или объединением. Однако, как необязательные значения, так и объединения имеют заранее известный максимальный размер и Zig может его использовать для своих дел. Тут дело в том, что рекурсивная структура не @@ -616,7 +616,7 @@ pub const User = struct { }; ``` -Может быть, Вам в Вашей практике рекурсивные структуры никогда и не понадобятся, +Может быть, вам в вашей практике рекурсивные структуры никогда и не понадобятся, но мы тут говорили не о моделировании данных, а об указателях, размещении данных в памяти для лучшего понимания того, с чем имеет дело компилятор. @@ -626,12 +626,12 @@ pub const User = struct { ускользающее от внимания, они не такие "конкретные", как, скажем, числа, строки или структуры. Чтобы двигаться дальше в изучении Zig, не требуется, чтобы всё, что касается указателей, было бы кристально ясным -для Вас. Однако, оттачивание мастерства в применении указателей того +для вас. Однако, оттачивание мастерства в применении указателей того стоит, и не только при изучении конкретно Zig. Всевозможные детали могут быть скрыты в таких языках, как Ruby, Python и JavaScript (и в меньше степени в таких, как C#, Java и Go), но от этого они никуда не -деваются. Их понимание влияет на то, как именно Вы пишете код и как этот +деваются. Их понимание влияет на то, как именно вы пишете код и как этот код будет выполняться. Так что не жалейте времени, упражняйтесь с примерами, добавляйте печать значений и адресов различных сущностей; чем -больше Вы исследуете, тем вопросы, связанные с указателями и их -применением, будут становиться для Вас всё более и более ясными. +больше вы исследуете, тем вопросы, связанные с указателями и их +применением, будут становиться для вас всё более и более ясными. diff --git a/src/ch05.md b/src/ch05.md index ebaefcd..c6675fe 100644 --- a/src/ch05.md +++ b/src/ch05.md @@ -5,7 +5,7 @@ переменными, данными и памятью. Но нам ещё нужно поговорить о том, как происходит управление ланными и памятью. Для коротко-живущих и простых скриптов это, как правило, не имеет особого значения. В наше время, имея -ноутбук, оснащённый 32-мя гигабайтами оперативной памяти, Вы можете +ноутбук, оснащённый 32-мя гигабайтами оперативной памяти, вы можете запустить свою программу, которая, делая какую-то работу (например, прочитать файл), может запросто использовать несколько сотен мегабайт памяти, сделать что-то потрясающее и далее выйти. По выходу операционная @@ -271,13 +271,13 @@ user`, а не `return &user`. Но это не всегда оказывает следующей главы. Прежде чем погрузиться в тему динамического выделения памяти, небольшое -забегание вперёд: в этой книге Вы увидите ещё один пример висячих +забегание вперёд: в этой книге вы увидите ещё один пример висячих указателей. К тому моменту мы уже будем знать достаточно, чтобы привести менее ... пример. К этой теме важно будет вернуться потому, что для разработчиков, которые раньше не имели дела с языками без автоматической сборки мусора (например, C), ручное управление памятью может поначалу показаться чем-то трудным, что чревато ошибками в программах и последующим отчаянием после попыток исправить программу. Но не надо -отчаиваться заранее, Вы получите то, что позволит лучше понять ручное +отчаиваться заранее, вы получите то, что позволит лучше понять ручное управление память, а пока мы скажем, что ключевой момент это чётко осознавать, где и когда существуют данные. diff --git a/src/ch06.md b/src/ch06.md index 897d610..76a6f29 100644 --- a/src/ch06.md +++ b/src/ch06.md @@ -19,9 +19,9 @@ Эта глава посвящена двум темам. Первая это общий обзор нашей третьей области памяти, кучи. А вот вторая более интересна - в языке Zig имеется концептуально простой, но тем не менее уникальный подход к управлению -динамической памятью. Даже если Вы так или иначе умеете работать с кучей +динамической памятью. Даже если вы так или иначе умеете работать с кучей (например, при помощи `malloc` и `free` из стандартной библиотеки языка -C), Вам всё равно следует проштудировать эту главу, поскольку в Zig есть +C), вам всё равно следует проштудировать эту главу, поскольку в Zig есть свои особенности. ## Динамическая память (heap, "куча") @@ -85,7 +85,7 @@ fn getRandomCount() !u8 { Общее правило при динамическом выделении памяти - каждый вызов `alloc`, как правило, имеет соответствующий вызов `free`. Первый выделяет память, -второй освобождает. Однако, не дайте этому примеру ограничить Ваше +второй освобождает. Однако, не дайте этому примеру ограничить ваше воображение. Да, этот паттерн, `try alloc` и сразу следом `defer free` действительно часто используется, и не зря: такое размещение инструкций является относительно надёжной защитой от ошибок в виде того, что мы @@ -108,7 +108,7 @@ fn getRandomCount() !u8 { блоком) до того момента, когда закончится выполняться блок (включая явный `return`), в котором находится `defer`. Такое отложенное выполнение вовсе не связано конкретно с аллокаторами и управлением памятью, это более -общий механизм, Вы можете использовать его для выполнения абсолютно +общий механизм, вы можете использовать его для выполнения абсолютно произвольного кода. `defer` в Zig подобен таковому в `Go`, с одним важным отличием: @@ -184,9 +184,9 @@ pub const Game = struct { Только что мы отметили, что не существует никаких правил относительно того, кто и когда должен освободить память. Однако, это верно лишь отчасти, всё таки имеется несколько важных правил, просто ничто, кроме -Вашей педантичности и аккуратности, не заставит Вас их соблюдать. +Вашей педантичности и аккуратности, не заставит вас их соблюдать. -Первое правило состоит, что Вы не можете освободить одну и ту же +Первое правило состоит, что вы не можете освободить одну и ту же область памяти дважды, смотрим на пример: ```zig @@ -213,7 +213,7 @@ pub fn main() !void { проектах со сложными правилами по поводу времён жизни бывает трудно всё это отследить. -Второе правило гласит, что Вы не можете освобождать память, на которую у +Второе правило гласит, что вы не можете освобождать память, на которую у Вас нет ссылки. Это вроде как самоочевидно, но бывает не всегда ясно, кто именно ответственен за освобождение. Вот функция, которая создаёт строку в нижнем регистре: @@ -274,7 +274,7 @@ fn isSpecial(allocator: Allocator, name: [] const u8) !bool { ## `create` и `destroy` Метод `alloc` структуры `std.mem.Allocator` возвращает срез с длиной, -указанной вторым параметром. Если Вам нужно одиночное значение, +указанной вторым параметром. Если вам нужно одиночное значение, вместо `alloc` и `free` используйте `create` и `destroy`, соответственно. Пару глав назад, изучая указатели, мы создавали пользователя и пытались увеличить его силу. Вот работающий пример с динамическим @@ -336,7 +336,7 @@ pub const User = struct { В этом случае имеет смысл возвращать `User`, а не указатель на него. Но иногда нужно, чтобы функция возвращала именно указатель на то, -что она создала. Так нужно делать, если Вы хотите, чтобы время +что она создала. Так нужно делать, если вы хотите, чтобы время жизни создаваемой сущности не было привязано к механизму стека вызовов. Чтобы решить проблему висячего указателя, можно использовать `create`: @@ -370,8 +370,8 @@ fn init(allocator: std.mem.Allocator, id: u64, power: i32) !*User{ ## Аллокаторы Одним из базовых принципов Zig является принцип "никаких скрытых -выделений памяти". В зависимости от Вашего предыдущего опыта -программирования, возможно, для Вас в этом принципе нет ничего +выделений памяти". В зависимости от вашего предыдущего опыта +программирования, возможно, для вас в этом принципе нет ничего особенного. Тем не менее, это сильно контрастирует с тем, что мы имеем в стандартной библиотеке языка C, а там для выделения памяти мы имеем `malloc`. В C для того, чтобы понять, использует ли та или иная функция @@ -400,7 +400,7 @@ defer allocator.free(say); Другая форма это когда аллокатор передаётся в "конструктор" (`init`), там запоминается и далее используется для внутренних нужд объекта. Мы видели такую форму выше, в реализации структуры `Game`. Такая форма менее явна, -потому что Вы знаете, что объект будет использовать динамическую память, +потому что вы знаете, что объект будет использовать динамическую память, но не знаете, в каких именно своих методах он будет это делать. Такой подход более практичен для долго живущих сущностей. @@ -413,10 +413,10 @@ defer allocator.free(say); `std.heap.GeneralPurposeAllocator`, но в стандартной библиотеке имеются реализации и других аллокаторов. -Если Вы разрабатываете библиотеку, лучше всего будет, если она будет -принимать аллокатор, это позволит пользователям Вашей библиотеки сами +Если вы разрабатываете библиотеку, лучше всего будет, если она будет +принимать аллокатор, это позволит пользователям вашей библиотеки сами выбирать, какой конкретно аллокатор будет использоваться. В противном -случае Вам будет нужно выбирать "правильный" аллокатор и, как мы далее +случае вам будет нужно выбирать "правильный" аллокатор и, как мы далее увидим, аллокаторы не являются взаимоисключающими - программа может одновременно использовать несколько видов аллокаторов, и на то могут быть свои весьма веские причины. @@ -426,7 +426,7 @@ defer allocator.free(say); Как и подразумевает само название этого аллокатора (`std.heap.GeneralPurposeAllocator`), это во всех отношениях аллокатор общего назначения, в частности, он безопасен для использования в -многопоточных программах и может служить главным аллокатором в Ваших +многопоточных программах и может служить главным аллокатором в ваших программах. Для большинства программ он будет единственным аллокатором. Аллокатор создаётся на старте программы и затем передаётся тем функциям, которым он нужен. Вот небольшой пример: @@ -460,7 +460,7 @@ pub fn main() !void { очередь, будет передавать аллокатор дальше по цепочке для своих функций, объектов и т.п. -Наверное, Вы обратили внимание, что синтаксис создания аллокатора +Наверное, вы обратили внимание, что синтаксис создания аллокатора какой-то странный. Что вообще такое `GeneralPurposeAllocator(.{}){}`? На самом деле всё это уже нам знакомо, просто тут оно сразу всё в кучу. `GeneralPurposeAllocator` это, очевидно, функция и, поскольку она @@ -477,7 +477,7 @@ var gpa = T{}; var gpa = std.heap.GeneralPurposeAllocator(.{}){}; ``` -Возможно, Вы всё ещё не уверены в смысле `.{}`. Это мы тоже встречали +Возможно, вы всё ещё не уверены в смысле `.{}`. Это мы тоже встречали раньше, это инициализатор структуры с неявным типом, тип будет выведен компилятором. А что это за тип и какие у него поля? Это `std.heap.general_purpose_allocator.Config`, хоть это явно у нас и не @@ -490,7 +490,7 @@ var gpa = std.heap.GeneralPurposeAllocator(.{}){}; ## Аллокатор для тестирования -Автор выражает надежду, что Вы серьезно призадумались, когда мы обсуждали +Автор выражает надежду, что вы серьезно призадумались, когда мы обсуждали утечки памяти и после заявления про то, что Zig тут может помочь, захотели узнать, чем именно. Помощь нам тут может оказать `std.testing.allocator`, который на данный момент реализовать на основе @@ -499,7 +499,7 @@ Zig, но это детали реализации. Нам будет важно использовать `std.testing.allocator` а наших тестах, он нам отловит большинство утечек памяти, если таковые будут. -Наверняка Вы уже знакомы с динамическими массивами, также часто называемых `ArrayList`. +Наверняка вы уже знакомы с динамическими массивами, также часто называемых `ArrayList`. Во многих языках с динамической типизацией вообще всё массивы динамические. Динамические массивы могут менять свою длину в процессе работы программы. В стандартной библиотеке Zig есть реализация обобщённого динамического массива, @@ -571,7 +571,7 @@ pub fn main() !void { ``` Код работает правильно и печатает то, что от него ожидается. Однако, -несмотря на то, что мы вызвали `deinit`, тут имеет место утечка. Если Вы +несмотря на то, что мы вызвали `deinit`, тут имеет место утечка. Если вы не заметили, где она происходит, ничего страшного, ведь сейчас мы напишем тест и будем в нём использовать `std.testing.allocator`: @@ -645,10 +645,10 @@ self.allocator.free(self.items); Распределитель памяти общего назначения, то есть `GeneralPurposeAllocator` это вполне разумный выбор по умолчанию, поскольку он в среднем хорошо работает для любых возможных сценариев и объёмов выделяемой,высвобождаемой памяти. -Однако, в какой-то конкретной программе Вы можете столкнуться с такой +Однако, в какой-то конкретной программе вы можете столкнуться с такой схемой выделения/освобождения, для которой будет более выгодно использовать специализированные аллокаторы. В качестве примера можно привести ситуацию, -когда Вам нужны какие-то относительно короткоживущие данные, +когда вам нужны какие-то относительно короткоживущие данные, которые хотелось бы удалить, так сказать, одним махом. Часто под такие требования подпадают парсеры. Вот скелет некой функции разбора каких-то данных: @@ -871,7 +871,7 @@ pub fn main() !void { вовсе не в том, чтобы полностью исключить использование кучи, это просто не сработает, поскольку все такие альтернативы имеют смысл в каких-то особых случаях. Но, тем не менее, -теперь в Вашем распоряжении есть много возможностей - +теперь в вашем распоряжении есть много возможностей - начиная от стековых кадров и вплоть до аллокатора общего назначения, со всеми промежуточными механизмами вроде статических буферов, потоковой передачи данных и специализированных аллокаторов. diff --git a/src/ch07.md b/src/ch07.md index 227e431..ff95607 100644 --- a/src/ch07.md +++ b/src/ch07.md @@ -100,7 +100,7 @@ fn IntArray(comptime length: usize) type { На первый взгляд, тут ничего "более изящного" нет. Однако, помимо того, что тут структура вложена в функцию и не имеет имени, она выглядит как любая другая структура, которые мы уже видели ранее, то есть у неё есть -поля, есть методы. Ну, Вы знаете, как говорят - *если что-то выглядит как +поля, есть методы. Ну, вы знаете, как говорят - *если что-то выглядит как утка...* тут то же самое, это что-то выглядит, плавает и крякает как обычная структура, потому что она и есть структура. @@ -138,7 +138,7 @@ fn List(comptime T: type) type { используются однобуквенные названия, например `K` и `V` для типа ключа и типа значения применительно к, скажем, хэш-таблице. -Если Вам ещё не до конца всё понятно, поглядите ещё раз на те места, где +Если вам ещё не до конца всё понятно, поглядите ещё раз на те места, где фигурирует это наше `T`: `items: []T` и `allocator.alloc(T, 4)`. Когда мы хотим использовать наш обобщённый тип (для создания конкретизированного), мы пишем diff --git a/src/ch08.md b/src/ch08.md index f889630..a38ec94 100644 --- a/src/ch08.md +++ b/src/ch08.md @@ -1,10 +1,10 @@ # Интерфейсы -Если Вы начали изучать Zig, скорее всего, пройдёт совсем немного времени -до того момента, когда Вы осознаете, что в нём нет никакого специального +Если вы начали изучать Zig, скорее всего, пройдёт совсем немного времени +до того момента, когда вы осознаете, что в нём нет никакого специального синтаксиса для создания интерфейсов (таких, как в Java или Go). Но, -вероятно, Вы заметите некоторые вещи, которые номинально не являются +вероятно, вы заметите некоторые вещи, которые номинально не являются таковыми, но очень на них похожи, например, `std.mem.Allocator`. Это потому, что в Zig действительно нет простого механизма для создания интерфейсов (например, ключевых слов `interface` и `implements`), но, тем @@ -104,7 +104,7 @@ fn writeAll(ptr: *anyopaque, data: []const u8) !void { в примере это `*File`. Мы как бы говорим компилятору: "дружище, верь мне, я знаю, что делаю - дай мне указатель, который показывает *туда же*, куда и `ptr`, но только теперь рассматривай содержимое -этой области памяти как `File`". Как Вы, наверное, поняли, `@ptrCast` это +этой области памяти как `File`". Как вы, наверное, поняли, `@ptrCast` это мощная и полезная штука, поскольку позволяет нам рассматривать какую-то область памяти как что угодно. Но если мы ошибёмся и преобразуем "универсальный" указатель к типу, который не соответствует тому, что @@ -119,7 +119,7 @@ fn writeAll(ptr: *anyopaque, data: []const u8) !void { точкой (`f32`) располагалось по адресу, кратному размеру этого числа, то есть четырём. Тип `anyopaque` всегда имеет фактор выравнивания, равный единице (то есть может располагаться по любому адресу). А вот у структуры -`File` фактор выравнивания равен четырём. Если хотите, Вы можете сами это +`File` фактор выравнивания равен четырём. Если хотите, вы можете сами это увидеть, напечатав значения `@alignOf(File)` и `@alignOf(anyopaque)`. Поэтому подобно тому, как нам нужна `@ptrCast` для указания компилятору, какой именно тип мы имеем ввиду, так и `@alignCast` нужна для того, @@ -317,7 +317,7 @@ builtin.Type{ `ptr_info.Pointer.child.writeAll(...)` транслируется в `File.writeAll(...)`, то есть получается именно то, что мы и хотели. -Если Вы посмотрите на другие применения такой витиеватой схемы, +Если вы посмотрите на другие применения такой витиеватой схемы, Вы там можете обнаружить, что `init` делает ещё кое-что после получения информации о типе: @@ -405,7 +405,7 @@ pub fn main() !void { это ограничение роли не играет. Вы можете построить, к примеру, объединение `Cache`, которое будет включать в себя все нужные реализации, например, `InMemory`, `Redis` и `Postgresql`. Если -появляются новые механизмы, Вы просто обновляете объединение. +появляются новые механизмы, вы просто обновляете объединение. Во многих случаях интерфейс будет вызывать функции с реализациями непосредственно. Для таких случаев можно использовать специальный @@ -421,6 +421,6 @@ switch (self) { Такой оборот `else` автоматически разворачивается, то есть для каждого варианта получается правильный тип `impl`. Здесь показана ещё одна вещь - интерфейсы могут привносить свою логику, в данном примере учитывается -пустая реализация (`.null`). Сколько логики Вы добавляете в сам -интерфейс, это Ваше дело, но по большей части интерфейсы должны в +пустая реализация (`.null`). Сколько логики вы добавляете в сам +интерфейс, это ваше дело, но по большей части интерфейсы должны в основном выполнять диспетчеризацию. diff --git a/src/ch09.md b/src/ch09.md index 288cbef..6478131 100644 --- a/src/ch09.md +++ b/src/ch09.md @@ -9,9 +9,9 @@ Начнём с рассмотрения ещё нескольких примеров висячих указателей. Может показаться странным, что мы опять к этому возвращаемся, -однако, если ранее Вы использовали только языки со сборкой мусора, +однако, если ранее вы использовали только языки со сборкой мусора, то, скорее всего, висячие указатели будут вызывать наибольшие трудности -из тех, с которыми Вы будете сталкиваться. +из тех, с которыми вы будете сталкиваться. Сможете догадаться, что напечатает следующий пример? @@ -57,7 +57,7 @@ Goku's power is: -1431655766 В этом примере мы знакомимся с обобщённой хэш-таблицей (`std.StringHashMap`), которая является специализилванной версией `std.AutoHashMap` с типом ключа `[]const u8`. Даже если не уверены на все -сто относительно того, что конкретно происходит при выводе, наверняка Вы +сто относительно того, что конкретно происходит при выводе, наверняка вы поняли, что это однозначно связано с тем, что второй вызов `print` делается *после* того, как мы уже удалили элемент из таблицы. Если убрать вызов `remove`, всё будет нормально. @@ -144,10 +144,10 @@ const User = struct { ## Владение Про хэш-таблицы знают все и всё ими пользуются, они имеют массу -применений, со многими из которых Вы, вероятно, имели дело в своей +применений, со многими из которых вы, вероятно, имели дело в своей практике. Хотя они и могут использоваться как короткоживущие сущности, но, как правило, они всё таки живут длительное время и поэтому требуют -столь же долгоживущих значений, которые Вы помещаете в таблицы. +столь же долгоживущих значений, которые вы помещаете в таблицы. В следующем примере хэш-таблица наполняется именами, которые пользователь вводит с клавиатуры в терминале. Пустое имя завершает цикл ввода. @@ -198,7 +198,7 @@ const User = struct { }; ``` -Программа учитывает регистр, но как бы аккуратно Вы не вводили "Leto", +Программа учитывает регистр, но как бы аккуратно вы не вводили "Leto", метод `contains` будет всегда возвращать `false`. Давайте попробуем отладить нашу программу, добавив для начала печать всех ключей и значений после цикла ввода: @@ -261,7 +261,7 @@ try lookup.put(owned_name, .{.power = i}); случае не меньше, чем хэш-таблица. Но есть одно но: теперь у нас появились утечки памяти. -Возможно, Вы подумали, что когда мы вызываем `lookup.deinit`, память под +Возможно, вы подумали, что когда мы вызываем `lookup.deinit`, память под ключи и значения будет освобождена сама собой. Увы, `StringHashMap` не может так сделать. Во-первых, ключи могут быть строковыми литералами, тогда строки хранятся в сегменте данных, а не в куче и их в принципе @@ -297,9 +297,9 @@ defer { Всё, достаточно с нас висячих указателей и управления памятью! То, что мы обсуждали в этом разделе по-прежнему может казаться не совсем ясным или -слишком абстрактным. Но, тем не менее, если Вы планируете писать что-то +слишком абстрактным. Но, тем не менее, если вы планируете писать что-то нетривиальное, можно совершенно определённо сказать, что управление -памятью это то, чем Вы будете должны овладеть в совершенстве. В этой +памятью это то, чем вы будете должны овладеть в совершенстве. В этой связи будет весьма полезно проделывать всяческие упражнения с кодом из этого раздела. Например, сделайте тип `UserLookup`, который содержал бы в себе весь код, связанный с динамической памятью. Попробуйте держать в @@ -310,6 +310,15 @@ defer { ## Динамический массив `ArrayList` +Определённо, вы будете рады узнать, что можно забыть про наши упражнения +с `IntList` и обобщённым вариантом `List`, потому что в стандартной +библиотеке Zig есть надлежащая реализация обобщённого динамического массива, +`std.ArrayList(T)`. + +Динамические массивы как таковые это вполне стандартная вещь, +но, поскольку эта структура данных весьма часто используется, +стоит поглядеть, каков `ArrayList` в действии: + ```zig const std = @import("std"); const builtin = @import("builtin"); @@ -374,6 +383,55 @@ const User = struct { }; ``` +В этом примере делается всё то же самое, как и ранее, но только вместо +`StringHashMap(User)` используется `ArrayList(User)`. Все правила +относительно времён жизни и работы с кучей по прежнему в силе. Обратите +внимание, что мы, как и раньше, делаем копию ключей при помощи `dupe` и, +как раньше, освобождаем память, занимаемую этими копиями, перед тем, как +вызвать `deinit` для `ArrayList`. + +И сейчас самое время сказать, что в Zig нет так называемых "свойств" +(properties) или приватных полей. Это можно видеть из кода, который +обращается к `arr.items` для прохода по значениям. Причина, по которой в +Zig нет свойств (это такие поля структуры/класса, которые снаружи +используются как обычные поля, но на самом деле это функции), состоит в +том, чтобы устранить возможный источник сюрпризов. В Zig, если что-то +выглядит как обращение к полю, это действительно обращение к полю и, +соответственно, если что-то не выглядит как вызов функции, то это не +вызов функции. С другой стороны, отсутствие приватных полей, возможно, +ошибка дизайна Zig, но мы можем как-то нивелировать, например, используя +символ `_` в качестве первого символа имен тех полей, которые +предназначены только для внутреннего использования. + +Поскольку строки имеют тип `[]8` или `[]const u8`, список из байтов +(то есть `ArrayList(u8)`) это подходящий тип для построения +конструктора строк по типу `StringBuilder` в .NET или `strings.Builder` в Go. +Фактически, вы будете часто использовать в случаях, когда функция +приниммает `Writer` и вам на выходе нужна строка. Ранее мы видели пример, +в котором для вывода документа JSON на стандартный вывод +использовалась `std.json.stringify`. Вот пример использования +`ArrayList(u8)` для вывода в переменную: + +```zig +const std = @import("std"); + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); + + var out = std.ArrayList(u8).init(allocator); + defer out.deinit(); + + try std.json.stringify(.{ + .this_is = "an anonymous struct", + .above = true, + .last_param = "are options", + }, .{.whitespace = .indent_2}, out.writer()); + + std.debug.print("{s}\n", .{out.items}); +} +``` + ## `anytype` ## `@typeOf` diff --git a/src/ex-ch09-05.zig b/src/ex-ch09-05.zig new file mode 100644 index 0000000..f5528aa --- /dev/null +++ b/src/ex-ch09-05.zig @@ -0,0 +1,18 @@ + +const std = @import("std"); + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); + + var out = std.ArrayList(u8).init(allocator); + defer out.deinit(); + + try std.json.stringify(.{ + .this_is = "an anonymous struct", + .above = true, + .last_param = "are options", + }, .{.whitespace = .indent_2}, out.writer()); + + std.debug.print("{s}\n", .{out.items}); +}