On branch main
modified: src/ch01.md modified: src/ch02.md modified: src/ch03.md modified: src/ch04.md modified: src/ch05.md modified: src/ch06.md modified: src/ch07.md modified: src/ch08.md modified: src/ch09.md new file: src/ex-ch09-05.zig
This commit is contained in:
42
src/ch01.md
42
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`? Мы уже видели подобную запись, когда немного говорили об
|
||||
|
||||
12
src/ch02.md
12
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`, если условие сразу же даёт ошибку, исполняется оборот
|
||||
|
||||
@@ -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 пробела. Как правило, текстовые редакторы можно
|
||||
|
||||
30
src/ch04.md
30
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), но от этого они никуда не
|
||||
деваются. Их понимание влияет на то, как именно Вы пишете код и как этот
|
||||
деваются. Их понимание влияет на то, как именно вы пишете код и как этот
|
||||
код будет выполняться. Так что не жалейте времени, упражняйтесь с
|
||||
примерами, добавляйте печать значений и адресов различных сущностей; чем
|
||||
больше Вы исследуете, тем вопросы, связанные с указателями и их
|
||||
применением, будут становиться для Вас всё более и более ясными.
|
||||
больше вы исследуете, тем вопросы, связанные с указателями и их
|
||||
применением, будут становиться для вас всё более и более ясными.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
переменными, данными и памятью. Но нам ещё нужно поговорить о том, как
|
||||
происходит управление ланными и памятью. Для коротко-живущих и простых
|
||||
скриптов это, как правило, не имеет особого значения. В наше время, имея
|
||||
ноутбук, оснащённый 32-мя гигабайтами оперативной памяти, Вы можете
|
||||
ноутбук, оснащённый 32-мя гигабайтами оперативной памяти, вы можете
|
||||
запустить свою программу, которая, делая какую-то работу (например,
|
||||
прочитать файл), может запросто использовать несколько сотен мегабайт
|
||||
памяти, сделать что-то потрясающее и далее выйти. По выходу операционная
|
||||
@@ -271,13 +271,13 @@ user`, а не `return &user`. Но это не всегда оказывает
|
||||
следующей главы.
|
||||
|
||||
Прежде чем погрузиться в тему динамического выделения памяти, небольшое
|
||||
забегание вперёд: в этой книге Вы увидите ещё один пример висячих
|
||||
забегание вперёд: в этой книге вы увидите ещё один пример висячих
|
||||
указателей. К тому моменту мы уже будем знать достаточно, чтобы привести
|
||||
менее ... пример. К этой теме важно будет вернуться потому, что для
|
||||
разработчиков, которые раньше не имели дела с языками без автоматической
|
||||
сборки мусора (например, C), ручное управление памятью может поначалу
|
||||
показаться чем-то трудным, что чревато ошибками в программах и
|
||||
последующим отчаянием после попыток исправить программу. Но не надо
|
||||
отчаиваться заранее, Вы получите то, что позволит лучше понять ручное
|
||||
отчаиваться заранее, вы получите то, что позволит лучше понять ручное
|
||||
управление память, а пока мы скажем, что ключевой момент это чётко
|
||||
осознавать, где и когда существуют данные.
|
||||
|
||||
48
src/ch06.md
48
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 {
|
||||
вовсе не в том, чтобы полностью исключить использование кучи,
|
||||
это просто не сработает, поскольку все такие альтернативы
|
||||
имеют смысл в каких-то особых случаях. Но, тем не менее,
|
||||
теперь в Вашем распоряжении есть много возможностей -
|
||||
теперь в вашем распоряжении есть много возможностей -
|
||||
начиная от стековых кадров и вплоть до аллокатора общего назначения,
|
||||
со всеми промежуточными механизмами вроде статических буферов,
|
||||
потоковой передачи данных и специализированных аллокаторов.
|
||||
|
||||
@@ -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)`. Когда мы
|
||||
хотим использовать наш обобщённый тип (для создания конкретизированного),
|
||||
мы пишем
|
||||
|
||||
18
src/ch08.md
18
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`). Сколько логики вы добавляете в сам
|
||||
интерфейс, это ваше дело, но по большей части интерфейсы должны в
|
||||
основном выполнять диспетчеризацию.
|
||||
|
||||
76
src/ch09.md
76
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`
|
||||
|
||||
18
src/ex-ch09-05.zig
Normal file
18
src/ex-ch09-05.zig
Normal file
@@ -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});
|
||||
}
|
||||
Reference in New Issue
Block a user