Функции и параметры

Разбираем, как Zig передаёт параметры и почему аргументы неизменяемы.

Параметры функции в Zig передаются по значению и неизменяемы внутри тела: чтобы менять данные вызывающего, передают указатель явно.

Функция в Zig объявляется через fn, принимает типизированные параметры и возвращает значение указанного типа. На первый взгляд всё как в C, но есть важные нюансы: параметры неизменяемы, а большие значения компилятор может передавать эффективно сам.

Базовое объявление

const std = @import("std");

fn add(a: i32, b: i32) i32 {
    return a + b;
}

pub fn main() void {
    std.debug.print("{d}\n", .{add(2, 3)});
}

Вывод:

5

Сигнатура читается слева направо: имя, параметры с типами, затем тип возврата. Если функция ничего не возвращает, пишут void.

Параметры неизменяемы

fn tryChange(x: i32) void {
    // x += 1; // ОШИБКА: параметр неизменяем
    const y = x + 1; // так можно: новая локальная переменная
    _ = y;
}

Внутри функции параметр нельзя изменить — он ведёт себя как const. Это убирает класс ошибок, когда функция случайно правит «свой» аргумент. Если нужно менять — заведите локальную копию или принимайте указатель.

Изменение через указатель

fn increment(p: *i32) void {
    p.* += 1; // меняем значение по указателю вызывающего
}

var n: i32 = 10;
increment(&n); // n станет 11

Чтобы функция изменила данные вызывающего, она принимает указатель *i32, а вызывающий передаёт адрес через &n. Это явный «выходной параметр»: глядя на сигнатуру, видно, что функция может что-то изменить.

Передача структур и срезов

Большие структуры разумно передавать по указателю, чтобы избежать копирования. Срезы передают по значению — это дёшево, ведь срез сам по себе лёгкий (указатель + длина). Для последовательностей почти всегда принимают []const T: он работает с массивом любой длины.

Как работает под капотом

Хотя семантически параметры передаются по значению, компилятор Zig вправе передать большую структуру по скрытому указателю, если это эффективнее и неотличимо для программиста (ведь менять её всё равно нельзя). Это оптимизация без изменения смысла. Неизменяемость параметров позволяет компилятору смело так поступать.

Частые ошибки

Первая — пытаться изменить параметр и удивляться ошибке: заведите локальную копию или принимайте указатель. Вторая — передавать огромную структуру по значению ради «чистоты» и терять на копировании, когда указатель уместнее. Третья — забыть & при передаче адреса в функцию, ожидающую указатель.

Итог

  • Функции объявляют через fn; тип возврата — после списка параметров.
  • Параметры передаются по значению и неизменяемы внутри тела.
  • Чтобы менять данные вызывающего, принимают указатель и передают адрес через &.
  • Срезы передают по значению (они лёгкие), большие структуры — обычно по указателю.
Проверьте себя
1. Можно ли изменить параметр функции внутри её тела в Zig?
AДа, как в C
BНет, параметры неизменяемы — нужна локальная копия или указатель
CТолько беззнаковые параметры
DТолько в release-сборке
2. Как функция может изменить переменную вызывающего?
AОбъявить параметр var
BПринять указатель *T и менять через p.*, вызывающий передаёт &n
CВернуть значение
DЭто невозможно