Introdução: quando os bytes se calam e a string não se forma
Imagine: você escreveu um servidor em Go que recebe dados de uma biblioteca em C. Ou está analisando um protocolo binário onde as strings são armazenadas no bom e velho estilo C — com um byte nulo de terminação (\\0). Você tenta simplesmente fazer string(byteArray) e obtém… lixo. Ou, pior ainda, bugs ocultos que aparecerão daqui a um mês em produção.
Familiar? Esta pergunta no Stack Overflow acumulou milhares de visualizações por um motivo. O problema de converter um zero-terminated byte array em string é um daqueles que quebram lanças até mesmo entre desenvolvedores Go experientes. Neste artigo, vamos analisá-lo do início ao fim: desde a solução simples até as armadilhas de desempenho, memória e Unicode.
Se você programa em Go e trabalha com arrays de bytes, CGO ou protocolos de rede — este artigo é para você. Vamos nessa!
O que é um zero-terminated byte array e por que é um problema em Go?
Zero-terminated byte array (ou null-terminated string) é um array de bytes cujo final é marcado por um byte nulo (0x00). É um legado da linguagem C, onde as strings são exatamente assim: um array de char terminado por \\0.
Em Go, strings são sequências imutáveis de bytes de comprimento arbitrário. O byte nulo dentro de uma string é um caractere comum, não indica o fim. Portanto, a conversão direta string([]byte{104, 101, 108, 108, 111, 0, 119, 111, 114, 108, 100}) resultará na string "hello\\x00world", e não em "hello".
É aqui que está a principal armadilha: se você simplesmente copiar os bytes, o caractere nulo permanecerá dentro da string. Isso pode levar a:
- Exibição incorreta em logs e interfaces de usuário
- Erros durante a serialização (JSON, XML)
- Problemas com hash e comparação de strings
- Vazamento de dados, se a string contiver informações confidenciais após o nulo
Solução nº 1: Método clássico com bytes.IndexByte
A maneira mais simples e clara é encontrar a posição do primeiro byte nulo e truncar o array até ele. Para isso, a função bytes.IndexByte da biblioteca padrão é ideal.
package main
import ( "bytes" "fmt")
func zeroTerminatedToString(data []byte) string { // Encontra o índice do primeiro byte nulo idx := bytes.IndexByte(data, 0) if idx == -1 { // Se não houver byte nulo — retorna tudo como está return string(data) } // Trunca o array até o índice encontrado return string(data[:idx])}
func main() { // Exemplo: string C "hello" com terminador nulo cArray := []byte{'h', 'e', 'l', 'l', 'o', 0, 'e', 'x', 't', 'r', 'a'} result := zeroTerminatedToString(cArray) fmt.Printf("Resultado: %q", result) // "hello" fmt.Printf("Comprimento: %d", len(result)) // 5}Este código é rápido, fácil de ler e cobre 80% dos cenários. Mas há um detalhe: bytes.IndexByte percorre todo o array até o primeiro nulo. Se o array for enorme e o byte nulo estiver no final — é uma operação O(n).
Quando esta solução não é adequada?
- Se o array garantir ter um byte nulo no início (string vazia) — precisa ser tratado separadamente
- Se o desempenho for crítico e você chamar esta função milhões de vezes
- Se os dados contiverem vários bytes nulos (por exemplo, um array de strings C)
Solução nº 2: Usando CGO e funções C (para os corajosos)
Se você trabalha diretamente com código C via CGO, pode chamar a função C padrão strlen. Isso é elegante, mas adiciona dependência de CGO e custos indiretos de chamada.
// #include <string.h>import "C"import "unsafe"
func cStringToString(cArray []byte) string { if len(