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
- Arrays em Zig
- Slices: O Conceito Mais Importante
- Strings em Zig
- Operações Comuns com Strings
- ArrayList: Arrays Dinâmicos
- Interoperabilidade com C
- Melhores Práticas
- Armadilhas Comuns
- FAQ
- 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ística | Descrição |
|---|---|
| Tamanho fixo | Definido em tempo de compilação |
| Tipo homogêneo | Todos elementos do mesmo tipo |
| Stack allocation | Alocados na stack (rápido) |
| Tamanho conhecido | array.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:
- Gerenciamento de Memória em Zig: Guia Completo — Aprofunde em allocators e gerenciamento de memória
- Como Criar uma CLI em Zig — Aplique strings e arrays em um projeto prático
- Tratamento de Erros em Zig — Aprenda a lidar com erros de parsing e alocação
- Parsing JSON em Zig — Manipulação avançada de strings com JSON
- Structs, Enums e Unions em Zig — Estruturas de dados mais complexas
Resumo
| Conceito | Sintaxe | Uso |
|---|---|---|
| Array | [5]i32 ou [_]i32 | Dados fixos na stack |
| Slice | []i32 ou []const i32 | Referência a sequência |
| String | []const u8 | Texto UTF-8 |
| ArrayList | std.ArrayList(T) | Array dinâmico |
| Slicing | array[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