Вивчаємо Zig Українською

У цій короткій частині ми розглянемо два правила написання коду, які використовуються компілятором, а також правила іменування стандартної бібліотеки.

Невикористані змінні

Zig не дозволяє змінним залишатися невикористаними. Наступне дає дві помилки під час компіляції:

learning.zig

const std = @import("std");

pub fn main() void {
  const sum = add(8999, 2);
}

fn add(a: i64, b: i64) i64 {
  // зверніть увагу, що a + a, не a + b
  return a + a;
}

Перша помилка полягає в тому, що sum є невикористаною локальною константою. Друга помилка полягає в тому, що b є невикористаним параметром функції. Для цього коду це очевидні помилки. Але у вас можуть бути законні причини мати невикористані змінні та параметри функції. У таких випадках ви можете призначити змінним підкреслення (_):

learning.zig

const std = @import("std");

pub fn main() void {
  _ = add(8999, 2);

  // або

  const sum = add(8999, 2);
  _ = sum;
}

fn add(a: i64, b: i64) i64 {
  _ = b;
  return a + a;
}

Як альтернативу виконанню _ = b;, ми могли б назвати параметр функції _, хоча, на мою думку, це залишає читача здогадуватися, що таке невикористаний параметр:

fn add(a: i64, _: i64) i64 {

Зауважте, що std також не використовується, але не створює помилки. Очікуйте, що колись у майбутньому Zig розглядатиме це як помилку під час компіляції.

Затінення (Shadowing)

Zig не дозволяє одному ідентифікатору "приховувати" інший за допомогою того самого імені. Цей код для читання з сокета недійсний:

fn read(stream: std.net.Stream) ![]const u8 {
  var buf: [512]u8 = undefined;
  const read = try stream.read(&buf);

  if (read == 0) {
    return error.Closed;
  }
  
  return buf[0..read];
}

Наша змінна read затінює назву нашої функції. Я не прихильник цього правила, оскільки воно зазвичай змушує розробників використовувати короткі безглузді імена. Наприклад, щоб компілювати цей код, я б змінив read на n. Це той випадок, коли, на мою думку, розробники мають набагато кращу позицію, щоб вибрати найбільш читабельний варіант.

Угода про найменування

Крім правил, встановлених компілятором, ви, звичайно, можете дотримуватися будь-якої угоди про іменування, якій ви віддаєте перевагу. Але це допомагає зрозуміти власну угоду про іменування Zig, оскільки більшість коду, з яким ви будете взаємодіяти, від стандартної бібліотеки до сторонніх бібліотек, використовує його.

Вихідний код Zig має відступ 4 пробілами. Я особисто використовую табуляцію, яка об’єктивно краща з точки зору доступності.

Назви функцій — верблюжий регістр, а змінні — нижній регістр_з_підкресленням (він же регістр змії). Типи: PascalCase. Існує цікавий перетин цих трьох правил. Змінні, які посилаються на тип, або функції, які повертають тип, дотримуються правила типу та іменуються в PascalCase. Ми вже це бачили, хоча ви могли не звернути на це увагу.

std.debug.print("{any}\n", .{@TypeOf(.{.year = 2023, .month = 8})});

Ми бачили інші вбудовані функції: @import, @rem і @intCast. Оскільки це функції, вони іменуються в верблюжому регістрі. @TypeOf також є вбудованою функцією, але використовується PascalCase, чому? Оскільки він повертає тип, тож використовується угода про найменування типів. Якби ми призначили результат @TypeOf змінній, використовуючи угоду про іменування Zig, ця змінна також має бути PascalCase:

const T = @TypeOf(3)
std.debug.print("{any}\n", .{T});

Виконуваний файл zig має команду fmt, яка, якщо задано файл або каталог, відформатує файл на основі власного посібника зі стилю Zig. Однак він не охоплює все, наприклад, він регулює відступи та положення дужок, але не змінює регістр ідентифікатора.