Slice vazio em Go: como não errar desde o início
\n\nTodo desenvolvedor Go, mais cedo ou mais tarde, se depara com a pergunta: "como inicializar corretamente um slice vazio?". Parece uma tarefa simples, mas não é tão simples assim. No FAQ oficial do Go e no Stack Overflow, centenas de comentários e milhares de votos são dedicados a este tópico. Por quê?
\n\nPorque em Go existem duas maneiras fundamentalmente diferentes de criar um slice vazio, e a escolha entre elas afeta o desempenho, a legibilidade do código e até mesmo o comportamento durante a serialização. Se você programa em Go e quer escrever código limpo e eficiente, sem surpresas — este artigo é para você.
\n\nVamos analisar:
\n- \n
- A diferença entre
var s []intes := make([]int, 0)\n - Quando usar nil-slice e quando usar empty slice \n
- Como isso afeta JSON, memória e desempenho \n
- O que dizem os gurus do Go e os autores da biblioteca padrão \n
Dois irmãos gêmeos: nil slice vs empty slice
\n\nEm Go, um slice é uma estrutura de três campos: ponteiro para o array, comprimento e capacidade. Quando falamos de slice vazio, nos referimos a um slice com comprimento 0. Mas a forma como o criamos faz diferença.
\n\nMétodo 1: Declaração var (nil slice)
\n\nvar s []int // s == nil, len(s) == 0, cap(s) == 0\n\nEste é um nil-slice. Ele não aponta para nenhum array na memória. No entanto, você pode usar append nele sem problemas — o Go alocará memória automaticamente.
Método 2: Inicialização com make (empty slice)
\n\ns := make([]int, 0) // s != nil, len(s) == 0, cap(s) == 0\n\nEste é um empty slice. Ele já aponta para um array de tamanho zero (um zerobase especial no runtime do Go).
\n\nÀ primeira vista, a diferença é imperceptível. Mas vamos aprofundar.
\n\nSaída no console e JSON — a primeira armadilha
\n\nTente executar este código:
\n\npackage main\n\nimport (\n "encoding/json"\n "fmt"\n)\n\nfunc main() {\n var nilSlice []int\n emptySlice := make([]int, 0)\n\n fmt.Printf("nilSlice: %v, is nil: %v\\n", nilSlice, nilSlice == nil)\n fmt.Printf("emptySlice: %v, is nil: %v\\n", emptySlice, emptySlice == nil)\n\n // Serialização JSON\n nilJSON, _ := json.Marshal(nilSlice)\n emptyJSON, _ := json.Marshal(emptySlice)\n\n fmt.Printf("nilSlice JSON: %s\\n", nilJSON)\n fmt.Printf("emptySlice JSON: %s\\n", emptyJSON)\n}\n\nO resultado vai te surpreender:
\n\nnilSlice: [], is nil: true\nemptySlice: [], is nil: false\nnilSlice JSON: null\nemptySlice JSON: []\n\nAí está a grande surpresa! A serialização JSON se comporta de forma diferente. Se sua API espera [] na resposta e você passa um nil-slice — o cliente receberá null. E isso quebra os contratos.
Muitos clientes REST (especialmente em JavaScript) verificam rigorosamente: se um array é esperado na resposta e chega null — pode ocorrer um erro ou a lógica no frontend pode ser comprometida.
Desempenho: há diferença?
\n\nA resposta curta: sim, mas em 99% dos casos é insignificante.
\n\nQuando você usa make([]int, 0), o Go aloca imediatamente memória para a estrutura do slice (24 bytes em sistemas 64 bits) e a vincula ao zerobase. Ao usar var, a alocação ocorre apenas no primeiro append.
Se você cria um slice que provavelmente permanecerá vazio, o nil-slice economiza uma alocação de memória. Se você sabe que adicionará elementos — make com especificação de capacidade oferece melhor desempenho:
// Se você sabe o tamanho aproximado — especifique a capacidade\ns := make([]int, 0, 100) // capacidade 100, comprimento 0\nfor i := 0; i < 100; i++ {\n s = append(s, i) // sem realocações!\n}\n\nMas para a maioria das tarefas aplicadas, a diferença é