Strings e Arrays em Zig: Guia Completo para Iniciantes

Strings e Arrays em Zig: Guia Completo

Arrays e strings são conceitos fundamentais em qualquer linguagem de programação, e Zig não é diferente. No entanto, Zig aborda esses tipos de dados de uma maneira única que oferece tanto performance quanto segurança. Neste guia completo, você vai aprender tudo sobre arrays, slices e strings em Zig — desde os conceitos básicos até técnicas avançadas de manipulação.

Índice

  1. Arrays em Zig
  2. Slices: O Conceito Mais Importante
  3. Strings em Zig
  4. Operações Comuns com Strings
  5. ArrayList: Arrays Dinâmicos
  6. Interoperabilidade com C
  7. Melhores Práticas
  8. Armadilhas Comuns
  9. FAQ
  10. Próximos Passos

Arrays em Zig

Arrays em Zig são coleções de elementos do mesmo tipo com tamanho fixo conhecido em tempo de compilação.

Sintaxe Básica

const std = @import("std");

pub fn main() void {
    // Array de 5 inteiros
    const numeros = [5]i32{ 1, 2, 3, 4, 5 };
    
    // Zig pode inferir o tamanho
    const letras = [_]u8{ 'a', 'b', 'c' };
    
    // Array com valor repetido
    const zeros = [_]i32{0} ** 10; // 10 zeros
    
    std.debug.print("Primeiro número: {d}\n", .{numeros[0]});
    std.debug.print("Array de letras: {s}\n", .{letras});
    std.debug.print("Tamanho do array de zeros: {d}\n", .{zeros.len});
}

Características dos Arrays

CaracterísticaDescrição
Tamanho fixoDefinido em tempo de compilação
Tipo homogêneoTodos elementos do mesmo tipo
Stack allocationAlocados na stack (rápido)
Tamanho conhecidoarray.len disponível em comptime

Acesso e Iteração

const std = @import("std");

pub fn main() void {
    const frutas = [_][]const u8{ "maçã", "banana", "laranja" };
    
    // Acesso por índice
    std.debug.print("Primeira fruta: {s}\n", .{frutas[0]});
    
    // Iteração com for
    for (frutas) |fruta| {
        std.debug.print("Fruta: {s}\n", .{fruta});
    }
    
    // Iteração com índice
    for (frutas, 0..) |fruta, i| {
        std.debug.print("{d}. {s}\n", .{ i + 1, fruta });
    }
}

Arrays Multidimensionais

const matriz = [3][3]i32{
    .{ 1, 2, 3 },
    .{ 4, 5, 6 },
    .{ 7, 8, 9 },
};

// Acesso
const elemento = matriz[1][2]; // 6

Slices: O Conceito Mais Importante

Se você vai aprender uma coisa sobre Zig, aprenda slices. Slices são a estrutura de dados mais usada e mais poderosa da linguagem.

O Que é um Slice?

Um slice é uma visão de uma sequência contígua de elementos. Ele contém:

  • Um ponteiro para o primeiro elemento
  • Um tamanho (length)
const std = @import("std");

pub fn main() void {
    const array = [_]i32{ 10, 20, 30, 40, 50 };
    
    // Criando um slice a partir de um array
    const slice = array[1..4]; // elementos 1, 2, 3 (índices 1 até 3)
    
    std.debug.print("Slice: {any}\n", .{slice});
    std.debug.print("Tamanho: {d}\n", .{slice.len});
    std.debug.print("Primeiro elemento: {d}\n", .{slice[0]});
}

Sintaxe de Slicing

const array = [_]i32{ 0, 1, 2, 3, 4, 5 };

const slice1 = array[0..3];    // [0, 1, 2]
const slice2 = array[2..];     // [2, 3, 4, 5] (do índice 2 até o final)
const slice3 = array[..3];     // [0, 1, 2] (do início até índice 3)
const slice4 = array[0..array.len]; // slice completo

Tipos de Slice

// Slice de elementos constantes (mais comum)
const slice_const: []const i32 = &[_]i32{1, 2, 3};

// Slice mutável (pode modificar elementos)
var array_mut = [_]i32{1, 2, 3};
const slice_mut: []i32 = array_mut[0..];

// Slice null-terminated (para interoperabilidade com C)
const slice_nt: [:0]const u8 = "hello";

Slices e Ownership

Slices não possuem os dados — eles apenas referenciam dados existentes:

const std = @import("std");

pub fn main() void {
    var dados = [_]u8{ 'a', 'b', 'c', 'd', 'e' };
    
    // slice1 e slice2 referenciam o mesmo array
    const slice1 = dados[0..3];
    const slice2 = dados[2..5];
    
    // Modificando através de um slice afeta o array original
    dados[2] = 'X';
    
    std.debug.print("slice1: {s}\n", .{slice1}); // "abX"
    std.debug.print("slice2: {s}\n", .{slice2}); // "Xde"
}

Strings em Zig

Aqui está uma verdade importante: Zig não tem um tipo string dedicado. Em Zig, strings são simplesmente slices de bytes UTF-8.

String Literais

const std = @import("std");

pub fn main() void {
    // String literal: []const u8
    const mensagem = "Olá, Zig!";
    
    // Tipo real da string
    const msg_tipada: []const u8 = "Olá";
    
    // String multilinha
    const poema = \\\n        Rosas são vermelhas,
        Violetas são azuis,
        Zig é rápido,
        E isso é ótimo!
    ;
    
    std.debug.print("{s}\n", .{mensagem});
    std.debug.print("{s}\n", .{poema});
}

Diferença: String Literal vs Slice

const std = @import("std");

pub fn main() void {
    // String literal: array de tamanho fixo
    const literal = "Zig";
    // tipo: *const [3:0]u8 (array de 3 bytes + null terminator)
    
    // Convertendo para slice
    const slice: []const u8 = literal;
    
    std.debug.print("Tamanho do literal: {d}\n", .{literal.len});
    std.debug.print("Tamanho do slice: {d}\n", .{slice.len});
}

Verificação de UTF-8

const std = @import("std");

pub fn main() !void {
    const texto_valido = "Olá, mundo! 🌍";
    const texto_invalido = \x80\x81\x82; // bytes inválidos em UTF-8
    
    // Verificar se é UTF-8 válido
    const valido = std.unicode.utf8ValidateSlice(texto_valido);
    const invalido = std.unicode.utf8ValidateSlice(texto_invalido);
    
    std.debug.print("Texto válido? {s}\n", .{if (valido) "sim" else "não"});
    std.debug.print("Inválido? {s}\n", .{if (invalido) "sim" else "não"});
}

Operações Comuns com Strings

Concatenação

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    const parte1 = "Olá";
    const parte2 = "Mundo";
    
    // Alocar memória para a string concatenada
    const resultado = try std.mem.concat(allocator, u8, &.{ parte1, ", ", parte2, "!" });
    defer allocator.free(resultado);
    
    std.debug.print("{s}\n", .{resultado}); // "Olá, Mundo!"
}

Busca e Substituição

const std = @import("std");

pub fn main() !void {
    const texto = "A raposa marrom salta sobre o cão preguiçoso";
    
    // Encontrar substring
    const indice = std.mem.indexOf(u8, texto, "marrom");
    if (indice) |idx| {
        std.debug.print("'marrom' encontrado no índice: {d}\n", .{idx});
    }
    
    // Verificar se contém
    const contem = std.mem.containsAtLeast(u8, texto, 1, "raposa");
    std.debug.print("Contém 'raposa'? {s}\n", .{if (contem) "sim" else "não"});
    
    // Contar ocorrências
    const contagem = std.mem.count(u8, texto, "o");
    std.debug.print("Número de 'o's: {d}\n", .{contagem});
}

Split (Dividir String)

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    const dados = "maçã,banana,laranja,uva";
    
    // Dividir por vírgula
    var iterador = std.mem.splitScalar(u8, dados, ',');
    
    std.debug.print("Frutas:\n");
    while (iterador.next()) |fruta| {
        std.debug.print("  - {s}\n", .{fruta});
    }
    
    // Split com múltiplos delimitadores
    const texto = "um;dois:três";
    var iter_multi = std.mem.splitAny(u8, texto, ";:");
    
    std.debug.print("\nDivisão múltipla:\n");
    while (iter_multi.next()) |parte| {
        std.debug.print("  [{s}]\n", .{parte});
    }
}

Trim (Remover Espaços)

const std = @import("std");

pub fn main() void {
    const texto = "   espaços extras   ";
    
    // Remover espaços do início e fim
    const trimado = std.mem.trim(u8, texto, " ");
    std.debug.print("'{}'\n", .{trimado}); // "espaços extras"
    
    // Remover whitespace (espaço, tab, newline)
    const whitespace = "\t\n  texto  \n\t";
    const limpo = std.mem.trim(u8, whitespace, &std.ascii.whitespace);
    std.debug.print("'{}'\n", .{limpo}); // "texto"
}

Comparação

const std = @import("std");

pub fn main() void {
    const a = "Zig";
    const b = "Zig";
    const c = "Rust";
    
    // Comparação lexicográfica
    const iguais = std.mem.eql(u8, a, b);
    const diferentes = std.mem.eql(u8, a, c);
    
    std.debug.print("a == b? {s}\n", .{if (iguais) "sim" else "não"});
    std.debug.print("a == c? {s}\n", .{if (diferentes) "sim" else "não"});
    
    // Ordem lexicográfica
    const ordem = std.mem.order(u8, a, c);
    std.debug.print("ordem(a, c) = {any}\n", .{ordem}); // .lt (menor que)
}

Formatação

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    const nome = "Zig";
    const versao = "0.13.0";
    
    // Formatar string
    const mensagem = try std.fmt.allocPrint(allocator, "{s} versão {s}", .{ nome, versao });
    defer allocator.free(mensagem);
    
    std.debug.print("{s}\n", .{mensagem});
    
    // Formatação com largura
    const tabela = try std.fmt.allocPrint(allocator, "| {s: <10} | {s: >10} |", .{ "Linguagem", "Performance" });
    defer allocator.free(tabela);
    
    std.debug.print("{s}\n", .{tabela});
}

ArrayList: Arrays Dinâmicos

Quando você precisa de um array que pode crescer, use ArrayList.

Uso Básico

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    // Criar ArrayList de inteiros
    var lista = std.ArrayList(i32).init(allocator);
    defer lista.deinit();
    
    // Adicionar elementos
    try lista.append(10);
    try lista.append(20);
    try lista.append(30);
    
    // Adicionar múltiplos elementos
    try lista.appendSlice(&.{ 40, 50, 60 });
    
    // Inserir em posição específica
    try lista.insert(1, 15);
    
    // Acessar elementos
    std.debug.print("Primeiro: {d}\n", .{lista.items[0]});
    std.debug.print("Tamanho: {d}\n", .{lista.items.len});
    
    // Iterar
    std.debug.print("Elementos:\n");
    for (lista.items) |item| {
        std.debug.print("  {d}\n", .{item});
    }
    
    // Remover elemento
    _ = lista.orderedRemove(2);
    
    // Limpar tudo
    lista.clearRetainingCapacity();
}

ArrayList de Strings

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    // ArrayList de strings (cada string precisa de alocação própria)
    var nomes = std.ArrayList([]const u8).init(allocator);
    defer {
        // Liberar cada string individualmente
        for (nomes.items) |nome| {
            allocator.free(nome);
        }
        nomes.deinit();
    }
    
    // Adicionar strings alocadas
    const nome1 = try allocator.dupe(u8, "Alice");
    const nome2 = try allocator.dupe(u8, "Bob");
    
    try nomes.append(nome1);
    try nomes.append(nome2);
    
    for (nomes.items) |nome| {
        std.debug.print("Olá, {s}!\n", .{nome});
    }
}

ArrayList como Buffer de String

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    // Usar ArrayList(u8) como StringBuilder
    var builder = std.ArrayList(u8).init(allocator);
    defer builder.deinit();
    
    // Construir string gradualmente
    try builder.appendSlice("Olá");
    try builder.append(',');
    try builder.append(' ');
    try builder.appendSlice("mundo");
    try builder.append('!');
    
    // Converter para slice
    const mensagem = builder.items;
    std.debug.print("{s}\n", .{mensagem});
    
    // Ou converter para string alocada
    const mensagem_owned = try allocator.dupe(u8, builder.items);
    defer allocator.free(mensagem_owned);
}

Interoperabilidade com C

Zig facilita a integração com código C, incluindo strings C (null-terminated).

Strings C (null-terminated)

const std = @import("std");

pub fn main() void {
    // String literal C (null-terminated)
    const c_str: [:0]const u8 = "Hello";
    
    // Converter para ponteiro C
    const c_ptr: [*c]const u8 = c_str;
    
    // Converter de volta para slice
    const slice: []const u8 = std.mem.span(c_ptr);
    
    std.debug.print("Slice: {s}\n", .{slice});
    std.debug.print("Tamanho: {d}\n", .{slice.len});
}

Memcpy e Memset

const std = @import("std");

pub fn main() void {
    var origem = [_]u8{ 'a', 'b', 'c', 'd', 'e' };
    var destino: [5]u8 = undefined;
    
    // Copiar memória
    @memcpy(&destino, &origem);
    std.debug.print("Destino: {s}\n", .{destino});
    
    // Preencher com valor
    @memset(&destino, 'X');
    std.debug.print("Após memset: {s}\n", .{destino});
}

Melhores Práticas

1. Use []const u8 para Parâmetros de Função

// ✅ Bom: aceita string literals e slices
fn imprimirMensagem(msg: []const u8) void {
    std.debug.print("{s}\n", .{msg});
}

// ❌ Ruim: só aceita arrays de tamanho específico
fn imprimirMensagemRuim(msg: *const [5]u8) void {
    std.debug.print("{s}\n", .{msg});
}

2. Documente Ownership

// `texto` é emprestado (borrowed) - não assume ownership
fn processarTexto(texto: []const u8) void {
    // apenas lê texto
}

// `texto` é owned - caller deve liberar
fn criarTexto(allocator: std.mem.Allocator) ![]u8 {
    return try allocator.dupe(u8, "novo texto");
}

3. Prefira Stack Allocation Quando Possível

// ✅ Stack allocation (rápido, automático)
var buffer: [1024]u8 = undefined;
const slice = buffer[0..100];

// ⚠️ Heap allocation (mais lento, precisa gerenciar)
var lista = std.ArrayList(u8).init(allocator);
defer lista.deinit();

4. Use std.mem para Operações

// ✅ Use funções da std
const iguais = std.mem.eql(u8, a, b);
const indice = std.mem.indexOf(u8, texto, "abc");
const trimado = std.mem.trim(u8, texto, " ");

// ❌ Não reimplemente manualmente
fn compararManual(a: []const u8, b: []const u8) bool {
    // ... código desnecessário
}

Armadilhas Comuns

1. Slices Inválidos (Use-After-Free)

// ❌ ERRADO: slice aponta para memória liberada
fn getSlice() []const u8 {
    var buffer: [100]u8 = undefined;
    return buffer[0..10]; // buffer é liberado ao retornar!
}

// ✅ CORRETO: usar allocator para memória persistente
fn getSliceAlocada(allocator: std.mem.Allocator) ![]const u8 {
    return try allocator.dupe(u8, "texto persistente");
}

2. Buffer Overflow

// ❌ ERRADO: escrita além do buffer
var buffer: [10]u8 = undefined;
@memcpy(buffer[0..20], dados[0..20]); // overflow!

// ✅ CORRETO: verificar tamanhos
const len = @min(buffer.len, dados.len);
@memcpy(buffer[0..len], dados[0..len]);

3. Esquecer de Liberar Memória

// ❌ ERRADO: memory leak
fn processar() !void {
    const texto = try allocator.dupe(u8, "dados");
    // esqueceu: defer allocator.free(texto);
}

// ✅ CORRETO: sempre liberar
fn processar() !void {
    const texto = try allocator.dupe(u8, "dados");
    defer allocator.free(texto);
    // ... usar texto
}

4. Modificar String Literals

// ❌ ERRADO: string literals são constantes
var texto = "imutável";
texto[0] = 'I'; // ERRO DE COMPILAÇÃO!

// ✅ CORRETO: criar cópia mutável
var buffer = try allocator.dupe(u8, "mutável");
defer allocator.free(buffer);
buffer[0] = 'M'; // OK

FAQ

Qual a diferença entre array e slice?

Arrays têm tamanho fixo conhecido em tempo de compilação. Slices são ponteiros + tamanho que podem referenciar qualquer sequência contígua de elementos, seja de arrays, heap, ou outras fontes.

Como converter string para inteiro?

const numero = try std.fmt.parseInt(i32, "42", 10);

Como converter inteiro para string?

var buffer: [20]u8 = undefined;
const texto = try std.fmt.bufPrint(&buffer, "{d}", .{42});

Strings são UTF-8 em Zig?

Sim, convenção é UTF-8, mas o tipo é apenas []const u8. Você é responsável por garantir validade UTF-8.

Como lidar com strings Unicode/avançadas?

Para processamento complexo de Unicode (grapheme clusters, normalização), use bibliotecas externas como ziglyph.

ArrayList vs Array?

  • Array: tamanho fixo, stack, mais rápido
  • ArrayList: tamanho dinâmico, heap, flexível

Próximos Passos

Agora que você domina arrays, slices e strings em Zig, recomendamos continuar com:

  1. Gerenciamento de Memória em Zig: Guia Completo — Aprofunde em allocators e gerenciamento de memória
  2. Como Criar uma CLI em Zig — Aplique strings e arrays em um projeto prático
  3. Tratamento de Erros em Zig — Aprenda a lidar com erros de parsing e alocação
  4. Parsing JSON em Zig — Manipulação avançada de strings com JSON
  5. Structs, Enums e Unions em Zig — Estruturas de dados mais complexas

Resumo

ConceitoSintaxeUso
Array[5]i32 ou [_]i32Dados fixos na stack
Slice[]i32 ou []const i32Referência a sequência
String[]const u8Texto UTF-8
ArrayListstd.ArrayList(T)Array dinâmico
Slicingarray[1..5]Criar sub-visão

Arrays, slices e strings são fundamentais em Zig. Dominar esses conceitos é essencial para escrever código Zig idiomático, seguro e performático. Pratique com os exemplos deste guia e consulte a documentação oficial para aprofundar ainda mais.


Escrito por Camila para ZigLang Brasil. Última atualização: 10 de fevereiro de 2026.

Achou algum erro? Tem sugestões? Contribua no GitHub

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.