Validador de CPF em Zig — Tutorial Passo a Passo
Neste tutorial, vamos construir um validador de CPF completo em Zig. Este é um projeto especialmente relevante para desenvolvedores brasileiros — o CPF (Cadastro de Pessoas Físicas) é onipresente em sistemas nacionais, e entender seu algoritmo de validação é fundamental.
O Que Vamos Construir
Nosso validador vai:
- Validar CPFs com o algoritmo oficial dos dígitos verificadores
- Aceitar CPF com e sem formatação (123.456.789-09 ou 12345678909)
- Detectar CPFs com todos os dígitos iguais (111.111.111-11, etc.)
- Formatar CPFs sem pontuação
- Gerar CPFs válidos aleatórios (útil para testes)
Por Que Este Projeto?
O algoritmo do CPF é um excelente exercício de manipulação de arrays, aritmética modular e validação de dados. Em Zig, ele demonstra como trabalhar com arrays de tamanho fixo (um CPF tem sempre 11 dígitos), iteração com índices e o padrão de separar normalização de validação.
Entendendo o Algoritmo
O CPF tem 11 dígitos: ABC.DEF.GHI-JK, onde J e K são dígitos verificadores calculados a partir dos 9 primeiros dígitos.
Primeiro dígito verificador (J):
- Multiplique cada um dos 9 primeiros dígitos por pesos decrescentes de 10 a 2
- Some os resultados
- Calcule o resto da divisão por 11
- Se o resto for menor que 2, J = 0; senão, J = 11 - resto
Segundo dígito verificador (K):
- Multiplique cada um dos 10 primeiros dígitos (incluindo J) por pesos de 11 a 2
- Repita os passos 2-4
Passo 1: Normalização
const std = @import("std");
const mem = std.mem;
const fmt = std.fmt;
const io = std.io;
/// Erros de validação de CPF.
const CpfError = error{
ComprimentoInvalido,
CaractereInvalido,
DigitosIguais,
DigitoVerificadorInvalido,
};
/// Representa um CPF normalizado (apenas dígitos).
/// Usamos um array fixo de 11 elementos porque todo CPF
/// válido tem exatamente 11 dígitos — o tipo codifica
/// essa invariante, tornando impossível ter um CPF com
/// comprimento errado após a normalização.
const Cpf = struct {
digitos: [11]u8,
/// Cria um Cpf a partir de uma string, removendo formatação.
/// Aceita tanto "123.456.789-09" quanto "12345678909".
pub fn deString(entrada: []const u8) CpfError!Cpf {
var digitos: [11]u8 = undefined;
var pos: usize = 0;
for (entrada) |c| {
// Ignora pontos, hífens e espaços (formatação)
if (c == '.' or c == '-' or c == ' ') continue;
// Verifica se é dígito
if (c < '0' or c > '9') return CpfError.CaractereInvalido;
if (pos >= 11) return CpfError.ComprimentoInvalido;
digitos[pos] = c - '0'; // Converte ASCII para valor numérico
pos += 1;
}
if (pos != 11) return CpfError.ComprimentoInvalido;
return Cpf{ .digitos = digitos };
}
/// Formata o CPF como string: XXX.XXX.XXX-XX
pub fn formatar(self: Cpf, buf: []u8) []u8 {
if (buf.len < 14) return buf[0..0];
var pos: usize = 0;
for (self.digitos, 0..) |d, i| {
if (i == 3 or i == 6) {
buf[pos] = '.';
pos += 1;
}
if (i == 9) {
buf[pos] = '-';
pos += 1;
}
buf[pos] = d + '0';
pos += 1;
}
return buf[0..pos];
}
/// Validação completa do CPF.
pub fn validar(self: Cpf) CpfError!void {
// 1. Rejeita CPFs com todos os dígitos iguais
try self.verificarDigitosIguais();
// 2. Verifica primeiro dígito verificador
const dv1 = self.calcularDigitoVerificador(9);
if (self.digitos[9] != dv1) return CpfError.DigitoVerificadorInvalido;
// 3. Verifica segundo dígito verificador
const dv2 = self.calcularDigitoVerificador(10);
if (self.digitos[10] != dv2) return CpfError.DigitoVerificadorInvalido;
}
/// Verifica se todos os dígitos são iguais.
/// CPFs como 111.111.111-11 passam na validação matemática
/// mas são considerados inválidos pela Receita Federal.
fn verificarDigitosIguais(self: Cpf) CpfError!void {
const primeiro = self.digitos[0];
for (self.digitos[1..]) |d| {
if (d != primeiro) return; // Pelo menos um diferente — OK
}
return CpfError.DigitosIguais;
}
/// Calcula um dígito verificador.
/// `n` é a posição do dígito (9 para o primeiro, 10 para o segundo).
fn calcularDigitoVerificador(self: Cpf, n: usize) u8 {
var soma: u32 = 0;
var peso: u32 = @intCast(n + 1);
for (self.digitos[0..n]) |d| {
soma += @as(u32, d) * peso;
peso -= 1;
}
const resto = soma % 11;
return if (resto < 2) 0 else @intCast(11 - resto);
}
};
Por que um array fixo [11]u8? Um CPF sempre tem 11 dígitos. Usar um array fixo em vez de um slice comunica essa invariante no tipo. O compilador pode otimizar melhor (sem indireção), e é impossível criar um Cpf com número errado de dígitos — o construtor deString garante isso.
Passo 2: Gerador de CPFs para Teste
/// Gera um CPF válido aleatório.
/// Útil para testes automatizados — nunca para fraude!
fn gerarCpfValido() Cpf {
var cpf = Cpf{ .digitos = undefined };
// Gera 9 dígitos aleatórios
for (0..9) |i| {
cpf.digitos[i] = @intCast(std.crypto.random.uintLessThan(u8, 10));
}
// Garante que não são todos iguais
var todos_iguais = true;
for (cpf.digitos[1..9]) |d| {
if (d != cpf.digitos[0]) {
todos_iguais = false;
break;
}
}
if (todos_iguais) {
cpf.digitos[8] = (cpf.digitos[0] + 1) % 10;
}
// Calcula dígitos verificadores
cpf.digitos[9] = cpf.calcularDigitoVerificador(9);
cpf.digitos[10] = cpf.calcularDigitoVerificador(10);
return cpf;
}
Passo 3: Mensagens de Erro
fn mensagemErro(err: CpfError) []const u8 {
return switch (err) {
CpfError.ComprimentoInvalido => "CPF deve ter exatamente 11 dígitos",
CpfError.CaractereInvalido => "CPF contém caracteres inválidos (use apenas dígitos, pontos e hífen)",
CpfError.DigitosIguais => "CPF com todos os dígitos iguais é inválido",
CpfError.DigitoVerificadorInvalido => "Dígito verificador inválido — CPF incorreto",
};
}
Passo 4: Interface Interativa
pub fn main() !void {
const stdout = io.getStdOut().writer();
const stdin = io.getStdIn().reader();
try stdout.print(
\\
\\ Validador de CPF Zig v1.0
\\ ═════════════════════════
\\ Comandos: gerar, sair
\\ Ou digite um CPF para validar.
\\
, .{});
var buf: [256]u8 = undefined;
var fmt_buf: [14]u8 = undefined;
while (true) {
try stdout.print("\n CPF> ", .{});
const linha = stdin.readUntilDelimiterOrEof(&buf, '\n') catch continue orelse break;
const entrada = mem.trim(u8, linha, " \t\r\n");
if (entrada.len == 0) continue;
if (mem.eql(u8, entrada, "sair")) break;
if (mem.eql(u8, entrada, "gerar")) {
try stdout.print("\n CPFs válidos gerados:\n", .{});
for (0..5) |_| {
const cpf = gerarCpfValido();
const formatado = cpf.formatar(&fmt_buf);
try stdout.print(" {s}\n", .{formatado});
}
continue;
}
// Validar CPF digitado
const cpf = Cpf.deString(entrada) catch |err| {
try stdout.print(" INVÁLIDO: {s}\n", .{mensagemErro(err)});
continue;
};
cpf.validar() catch |err| {
const formatado = cpf.formatar(&fmt_buf);
try stdout.print(" INVÁLIDO: {s} — {s}\n", .{ formatado, mensagemErro(err) });
continue;
};
const formatado = cpf.formatar(&fmt_buf);
try stdout.print(" VÁLIDO: {s}\n", .{formatado});
}
try stdout.print("\nAté logo!\n", .{});
}
Passo 5: Testes Abrangentes
test "CPF válido sem formatação" {
const cpf = try Cpf.deString("12345678909");
try cpf.validar();
}
test "CPF válido com formatação" {
const cpf = try Cpf.deString("123.456.789-09");
try cpf.validar();
}
test "CPF com dígitos iguais" {
const cpf = try Cpf.deString("111.111.111-11");
try std.testing.expectError(CpfError.DigitosIguais, cpf.validar());
}
test "CPF com dígito verificador errado" {
const cpf = try Cpf.deString("123.456.789-00");
try std.testing.expectError(CpfError.DigitoVerificadorInvalido, cpf.validar());
}
test "CPF curto demais" {
try std.testing.expectError(CpfError.ComprimentoInvalido, Cpf.deString("1234"));
}
test "CPF com letra" {
try std.testing.expectError(CpfError.CaractereInvalido, Cpf.deString("123.456.789-AB"));
}
test "formatação de CPF" {
const cpf = try Cpf.deString("12345678909");
var buf: [14]u8 = undefined;
const formatado = cpf.formatar(&buf);
try std.testing.expectEqualStrings("123.456.789-09", formatado);
}
test "CPF gerado é válido" {
// Gera e valida vários CPFs aleatórios
for (0..100) |_| {
const cpf = gerarCpfValido();
try cpf.validar();
}
}
test "cálculo de dígito verificador" {
const cpf = try Cpf.deString("12345678909");
try std.testing.expectEqual(@as(u8, 0), cpf.calcularDigitoVerificador(9));
try std.testing.expectEqual(@as(u8, 9), cpf.calcularDigitoVerificador(10));
}
Compilando e Executando
zig build test
zig build run
Exemplo de Uso
CPF> 123.456.789-09
VÁLIDO: 123.456.789-09
CPF> 111.111.111-11
INVÁLIDO: 111.111.111-11 — CPF com todos os dígitos iguais é inválido
CPF> 123.456.789-00
INVÁLIDO: 123.456.789-00 — Dígito verificador inválido — CPF incorreto
CPF> gerar
CPFs válidos gerados:
847.253.196-04
361.928.574-30
...
Conceitos Aprendidos
- Arrays de tamanho fixo para dados com invariantes de comprimento
- Aritmética modular para dígitos verificadores
- Separação entre normalização e validação
- Geração de dados válidos para testes
- Tratamento de erros com union de erros
Próximos Passos
- Aprenda sobre validação de dados para outros documentos brasileiros
- Implemente validação de CNPJ (mesmo algoritmo, 14 dígitos)
- Construa o próximo projeto: Relógio Digital