Zig é uma linguagem de programação moderna projetada para ser robusta, otimizada e de fácil manutenção. Com seu foco em desempenho, controle explícito sobre a alocação de memória e recursos de tempo de compilação, Zig é uma excelente escolha para construir aplicações de alto desempenho, incluindo APIs REST que precisam lidar com cargas significativas com uso mínimo de recursos.
Neste tutorial, vamos explorar como aproveitar o Zig para criar uma API REST que não seja apenas funcional, mas "incrivelmente rápida." Vamos passar pelas etapas de configuração de um projeto, implementação da funcionalidade principal da API e otimização para desempenho máximo. Ao final, você terá uma base sólida para construir serviços web de alto desempenho usando Zig.

O Apidog combina documentação de API, design, teste e depuração em um único fluxo de trabalho contínuo, tornando-o perfeito para testar nossa API Zig de alto desempenho.

Configurando Seu Ambiente de Desenvolvimento Zig
Antes de começarmos a codificar, vamos garantir que você tenha tudo o que precisa para desenvolver com Zig:
Instale o Zig: Baixe a versão mais recente em ziglang.org ou use um gerenciador de pacotes:
# No macOS com Homebrew
brew install zig
# No Linux com apt
sudo apt install zig
Verifique a instalação:
zig version
Estrutura do Projeto
Vamos criar uma estrutura de projeto limpa:
mkdir zig-rest-api
cd zig-rest-api
zig init-exe
Isso cria um projeto Zig básico com um arquivo src/main.zig
. Vamos expandir essa estrutura:
zig-rest-api/
├── src/
│ ├── main.zig
│ ├── server.zig
│ ├── router.zig
│ ├── handlers/
│ │ ├── users.zig
│ │ └── products.zig
│ └── models/
│ ├── user.zig
│ └── product.zig
├── build.zig
└── README.md
Implementação do Servidor HTTP
Primeiro, vamos criar um servidor HTTP básico. A biblioteca padrão do Zig fornece primitivas de rede, mas não inclui um servidor HTTP completo. Usaremos o excelente pacote zhttp
.
Adicione esta dependência ao seu build.zig
:
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const zhttp = b.dependency("zhttp", .{});
const exe = b.addExecutable(.{
.name = "zig-rest-api",
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
exe.addModule("zhttp", zhttp.module("zhttp"));
b.installArtifact(exe);
}
Agora vamos implementar nosso servidor em src/server.zig
:
const std = @import("std");
const zhttp = @import("zhttp");
const Allocator = std.mem.Allocator;
pub const Server = struct {
server: zhttp.Server,
allocator: Allocator,
pub fn init(allocator: Allocator, port: u16) !Server {
return Server{
.server = try zhttp.Server.init(allocator, .{
.address = "0.0.0.0",
.port = port,
}),
.allocator = allocator,
};
}
pub fn deinit(self: *Server) void {
self.server.deinit();
}
pub fn start(self: *Server) !void {
std.log.info("Servidor ouvindo na porta {d}", .{self.server.port});
try self.server.start();
}
};
Adicionando Roteador e Manipulação de Requisições
Em src/router.zig
, vamos criar um roteador simples:
const std = @import("std");
const zhttp = @import("zhttp");
const Allocator = std.mem.Allocator;
pub const Router = struct {
routes: std.StringHashMap(HandlerFn),
allocator: Allocator,
pub const HandlerFn = fn(zhttp.Request, *zhttp.Response) anyerror!void;
pub fn init(allocator: Allocator) Router {
return Router{
.routes = std.StringHashMap(HandlerFn).init(allocator),
.allocator = allocator,
};
}
pub fn deinit(self: *Router) void {
self.routes.deinit();
}
pub fn addRoute(self: *Router, path: []const u8, handler: HandlerFn) !void {
try self.routes.put(try self.allocator.dupe(u8, path), handler);
}
pub fn handleRequest(self: *Router, req: zhttp.Request, res: *zhttp.Response) !void {
const handler = self.routes.get(req.path) orelse {
res.status = .not_found;
try res.send("Não encontrado");
return;
};
try handler(req, res);
}
};
Manipulação de JSON
Vamos criar funções utilitárias para trabalhar com JSON:
// src/json_utils.zig
const std = @import("std");
pub fn parseJson(comptime T: type, json_str: []const u8, allocator: std.mem.Allocator) !T {
var tokens = std.json.TokenStream.init(json_str);
return try std.json.parse(T, &tokens, .{
.allocator = allocator,
.ignore_unknown_fields = true,
});
}
pub fn stringify(value: anytype, allocator: std.mem.Allocator) ![]const u8 {
return std.json.stringifyAlloc(allocator, value, .{});
}
Construindo Manipuladores de API
Agora vamos criar um manipulador para recursos de usuário em src/handlers/users.zig
:
const std = @import("std");
const zhttp = @import("zhttp");
const json_utils = @import("../json_utils.zig");
const User = struct {
id: usize,
name: []const u8,
email: []const u8,
};
// Armazenamento em memória para demonstração
var users = std.ArrayList(User).init(std.heap.page_allocator);
pub fn getUsers(req: zhttp.Request, res: *zhttp.Response) !void {
const json = try json_utils.stringify(users.items, req.allocator);
res.headers.put("Content-Type", "application/json") catch unreachable;
try res.send(json);
}
pub fn getUserById(req: zhttp.Request, res: *zhttp.Response) !void {
// Extrair ID dos parâmetros da URL
const id_str = req.params.get("id") orelse {
res.status = .bad_request;
try res.send("Parâmetro ID ausente");
return;
};
const id = try std.fmt.parseInt(usize, id_str, 10);
// Encontrar usuário com ID correspondente
for (users.items) |user| {
if (user.id == id) {
const json = try json_utils.stringify(user, req.allocator);
res.headers.put("Content-Type", "application/json") catch unreachable;
try res.send(json);
return;
}
}
res.status = .not_found;
try res.send("Usuário não encontrado");
}
pub fn createUser(req: zhttp.Request, res: *zhttp.Response) !void {
const body = try req.body();
var user = try json_utils.parseJson(User, body, req.allocator);
// Gerar um novo ID
user.id = users.items.len + 1;
try users.append(user);
res.status = .created;
res.headers.put("Content-Type", "application/json") catch unreachable;
const json = try json_utils.stringify(user, req.allocator);
try res.send(json);
}
Colocando Tudo Junto
Agora, vamos atualizar nosso src/main.zig
para integrar tudo:
const std = @import("std");
const Server = @import("server.zig").Server;
const Router = @import("router.zig").Router;
const users = @import("handlers/users.zig");
pub fn main() !void {
// Criar um alocador de arena
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
var router = Router.init(allocator);
defer router.deinit();
// Registrar rotas
try router.addRoute("/api/users", users.getUsers);
try router.addRoute("/api/users/{id}", users.getUserById);
try router.addRoute("/api/users", users.createUser);
var server = try Server.init(allocator, 8080);
defer server.deinit();
// Configurar o manipulador de requisições
server.server.setRequestHandler(handler);
// Iniciar o servidor
try server.start();
}
fn handler(req: zhttp.Request, res: *zhttp.Response) !void {
std.log.info("{s} {s}", .{@tagName(req.method), req.path});
try router.handleRequest(req, res);
}
Otimizações de Desempenho
O Zig fornece vários recursos que tornam nossa API incrivelmente rápida:
- Abstrações de custo zero: A abordagem do Zig garante que as abstrações não adicionem sobrecarga.
- Metaprogramação em tempo de compilação: Podemos realizar trabalho em tempo de compilação em vez de em tempo de execução.
- Gerenciamento manual de memória: Ao controlar as alocações, evitamos pausas na coleta de lixo.
- Alocadores de arena: Perfeito para memória com escopo de requisição que pode ser rapidamente liberada toda de uma vez.
- Funções em linha: Caminhos críticos podem ser in-lineados para eliminar a sobrecarga de chamadas de função.
Vamos otimizar nosso roteador:
// Adicionar atributo in-line para funções críticas
pub inline fn handleRequest(self: *Router, req: zhttp.Request, res: *zhttp.Response) !void {
// Código existente...
}
E otimizar nosso manipulador de requisições com um pool de threads:
const ThreadPool = struct {
threads: []std.Thread,
pub fn init(thread_count: usize) !ThreadPool {
var threads = try std.heap.page_allocator.alloc(std.Thread, thread_count);
for (threads) |*thread, i| {
thread.* = try std.Thread.spawn(.{}, workerFn, .{i});
}
return ThreadPool{ .threads = threads };
}
fn workerFn(id: usize) void {
// Processar requisições recebidas
while (true) {
// Obter requisição da fila e processar
}
}
};
Conclusão
Neste tutorial, exploramos como aproveitar os recursos focados em desempenho do Zig para construir uma API REST de alto desempenho. Ao tirar proveito do gerenciamento manual de memória do Zig, recursos em tempo de compilação e abstrações de custo zero, criamos um servidor de API que é robusto e incrivelmente rápido.
Cobrimos:
- Configurando um ambiente de desenvolvimento Zig
- Construindo um servidor HTTP básico
- Criando um roteador para lidar com endpoints de API
- Implementando serialização e desserialização de JSON
- Adicionando manipuladores de endpoints de API
- Otimização para desempenho
Essa base lhe dá as ferramentas para construir APIs prontas para produção em Zig que superam aquelas escritas em muitas outras linguagens. À medida que você continuar desenvolvendo com Zig, explore recursos mais avançados como comptime, tratamento de erros e cross-compilation para aprimorar ainda mais suas aplicações.