diff --git a/src/ch09.md b/src/ch09.md index 6478131..ba5183e 100644 --- a/src/ch09.md +++ b/src/ch09.md @@ -403,14 +403,14 @@ Zig нет свойств (это такие поля структуры/кла символ `_` в качестве первого символа имен тех полей, которые предназначены только для внутреннего использования. -Поскольку строки имеют тип `[]8` или `[]const u8`, список из байтов -(то есть `ArrayList(u8)`) это подходящий тип для построения -конструктора строк по типу `StringBuilder` в .NET или `strings.Builder` в Go. +Поскольку строки имеют тип `[]8` или `[]const u8`, список из байтов (то +есть `ArrayList(u8)`) это подходящий тип для построения конструктора +строк по типу `StringBuilder` в .NET или `strings.Builder` в Go. Фактически, вы будете часто использовать в случаях, когда функция приниммает `Writer` и вам на выходе нужна строка. Ранее мы видели пример, -в котором для вывода документа JSON на стандартный вывод -использовалась `std.json.stringify`. Вот пример использования -`ArrayList(u8)` для вывода в переменную: +в котором для вывода документа JSON на стандартный вывод использовалась +`std.json.stringify`. Вот пример использования `ArrayList(u8)` для вывода +в переменную: ```zig const std = @import("std"); @@ -434,6 +434,92 @@ pub fn main() !void { ## `anytype` +Мы уже вскользь упоминали `anytype` в первой главе. Такой "тип" это +весьма полезеная форма утиной (неявной) типизации во время компиляции. +Вот простой логгер: + +```zig +pub const Logger = struct { + level: Level, + + // "error" is reserved, names inside an @"..." are always + // treated as identifiers + const Level = enum { + debug, + info, + @"error", + fatal, + }; + + fn info(logger: Logger, msg: []const u8, out: anytype) !void { + if (@intFromEnum(logger.level) <= @intFromEnum(Level.info)) { + try out.writeAll(msg); + } + } +}; +``` + +Параметр `out` метода `info` имеет тип `anytype`. +Это означает, что `Logger` может использовать +любую структуру, у которой есть метод `writeAll`, +у которого на входе `[]const u8` и на выходе `!void`. +Проверка типов производится во время компиляции +(а не во время выполнения программы): для каждого +типа, использованного в качестве второго параметра `Logger.info`, +проверяется, есть ли у этого типа метод `writeAll`. +Если мы попытаемся вызвать `info` с типом, который +не имеет всех нужных функций (в нашем случае одной), +то мы получим ошибку компиляции: + +```zig +var l = Logger{.level = .info}; +try l.info("sever started", true); +``` + +Компилятор скажет, что у типа `bool` нет поля с именем `writeAll`. +Использование `writer` типа `ArrayList(u8)` (например) работает: + +```zig +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); + + var l = Logger{.level = .info}; + + var arr = std.ArrayList(u8).init(allocator); + defer arr.deinit(); + + try l.info("sever started", arr.writer()); + std.debug.print("{s}\n", .{arr.items}); +} +``` + +Одним из существенных недостатков типа `anytype` является документация. +Давайте взглянем на сигнатуру функции `std.json.stringify`, которую мы +уже несколько раз использовали: + +```zig +// I **hate** multi-line function definitions +// But I'll make an exception for a guide which +// you might be reading on a small screen. + +fn stringify( + value: anytype, + options: StringifyOptions, + out_stream: anytype +) @TypeOf(out_stream).Error!void +``` + +Первый параметр, `value: anytype` вроде как очевиден: это что-то, что +нужно сериализовать и это что-то может быть чем угодно (на самом деле в +Zig существуют некоторые вещи, которые сериализатор JSON сериализовать не +сможет). А вот что касается третьего параметра, то, конечно, мы можем +догадываться, что это то, *куда* будет выведен документ, но для того, +чтобы понять, какие у этой сущности должны быть методы, нужно или глядеть +в исходный текст `stringify` или передать что-то первое под руку +попавшееся и использовать в качестве документации текст ошибок +компиляции. + ## `@typeOf` ## Система сборки diff --git a/src/ex-ch09-06.zig b/src/ex-ch09-06.zig new file mode 100644 index 0000000..a22965a --- /dev/null +++ b/src/ex-ch09-06.zig @@ -0,0 +1,34 @@ + +const std = @import("std"); + +pub const Logger = struct { + level: Level, + + // "error" is reserved, names inside an @"..." are always + // treated as identifiers + const Level = enum { + debug, + info, + @"error", + fatal, + }; + + fn info(logger: Logger, msg: []const u8, out: anytype) !void { + if (@intFromEnum(logger.level) <= @intFromEnum(Level.info)) { + try out.writeAll(msg); + } + } +}; + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); + + var l = Logger{.level = .info}; + + var arr = std.ArrayList(u8).init(allocator); + defer arr.deinit(); + + try l.info("sever started", arr.writer()); + std.debug.print("{s}\n", .{arr.items}); +}