Estilização

Nesta breve parte, abordaremos duas regras de codificação impostas pelo compilador, bem como a convenção de nomenclatura da biblioteca padrão.

Variáveis não utilizadas

Zig não permite que variáveis fiquem sem uso. O seguinte código resultará em dois erros de compilação:

const std = @import("std");

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

fn add(a: i64, b: i64) i64 {
	// atenção ao fato que isto é a + a, não a + b
	return a + a;
}

O primeiro erro ocorre porque sum é uma constante local não utilizada (unused local constant). O segundo erro ocorre porque b é um parâmetro de função não utilizado (unused function parameter). Para este código, esses são bugs óbvios. No entanto, você pode ter razões legítimas para ter variáveis e parâmetros de função não utilizados. Nestes casos, você pode atribuir as variáveis ao sublinhado (_):

const std = @import("std");

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

	// ou

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

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

Como alternativa a fazer _ = b;, poderíamos ter nomeado o parâmetro da função como _, embora, na minha opinião, isso deixe o leitor adivinhando qual é o parâmetro não utilizado:

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

Observe que std também está sem uso, mas não gera um erro. Em algum momento no futuro, espera-se que o Zig também trate isso como um erro de compilação.

Sombreamento

Zig não permite que um identificador "oculte" outro usando o mesmo nome. Este código, para ler de um socket, não é válido:

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];
}

A nossa variável read oculta o nome da nossa função. Eu não sou fã dessa regra, pois geralmente leva os desenvolvedores a usarem nomes curtos e sem significado. Por exemplo, para fazer com que este código compile, eu mudaria read para n. Esta é uma situação em que, na minha opinião, os desenvolvedores estão em uma posição muito melhor para escolher a opção mais legível.

Convenções de Nomenclatura

Além das regras impostas pelo compilador, você é, obviamente, livre para seguir qualquer convenção de nomenclatura que preferir. Mas ajuda a entender a convenção de nomenclatura do próprio Zig, já que grande parte do código com o qual você irá interagir, desde a biblioteca padrão até bibliotecas de terceiros, nos torna parte dela.

O código-fonte Zig é recuado com 4 espaços. Eu pessoalmente uso um tab porque é melhor para acessibilidade.

Os nomes das funções são camelCase e as variáveis são snake_case. Os tipos são PascalCase. Há uma intersecção interessante entre essas três regras. Variáveis que fazem referência a um tipo, ou funções que retornam um tipo, seguem a regra de tipo e são PascalCase. Já vimos isso, embora você possa não ter percebido.

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

Vimos outras funções integradas: @import, @rem e @intCast. Como essas são funções, elas são camelCase. @TypeOf também é uma função interna, mas é PascalCase, por quê? Porque retorna um tipo e, portanto, a convenção de nomenclatura de tipo é usada. Se atribuíssemos o resultado de @TypeOf a uma variável, usando a convenção de nomenclatura do Zig, essa variável também deveria ser PascalCase:

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

O executável zig possui um comando fmt que, dado um arquivo ou diretório, formatará o arquivo com base no guia de estilo do próprio Zig. Porém, ele não cobre tudo, por exemplo, ajustará o recuo e as posições dos colchetes, mas não irá alternar entre identificados maiúsculos e minúsculos.