Um slice em um exemplo
Um slice é uma visão expansível sobre um array. Você cria um com um literal ou com make, aumenta com append e lê o tamanho com len.
[]string não tem tamanho entre os colchetes. Essa é a diferença para um tipo array como [4]string, cujo tamanho é fixo.
O que é um slice
Um valor de slice é um cabeçalho pequeno com três campos:
- um ponteiro para um elemento de um array subjacente
- um tamanho (length): quantos elementos o slice enxerga
- uma capacidade (capacity): quantos elementos existem desse ponteiro até o fim do array subjacente
Copiar um slice (atribuí-lo, passá-lo para uma função) copia só esse cabeçalho. As duas cópias apontam para os mesmos elementos. Quase toda surpresa com slices vem de esquecer isso.
make: tamanho e capacidade
make([]T, len, cap) aloca um array subjacente e devolve um slice sobre ele. A capacidade é opcional e, por padrão, é igual ao tamanho.
Uma confusão comum: make([]int, 5) seguido de cinco chamadas de append dá dez elementos, sendo os cinco primeiros zero. Use make([]int, 0, 5) quando for fazer append, ou make([]int, 5) quando for atribuir por índice.
Pré-alocar a capacidade quando você sabe o tamanho final evita crescimentos repetidos. É uma otimização, não uma exigência.
append e crescimento
O append acrescenta elementos depois do tamanho atual. Se a capacidade for suficiente, ele escreve no lugar. Se não for, aloca um array maior, copia os elementos antigos e devolve um slice que aponta para o array novo.
A capacidade mais ou menos dobra enquanto o slice é pequeno e cresce por um fator menor (tendendo a 1,25x) depois de passar de 256 elementos. Os números exatos são um detalhe do runtime que já mudou entre versões do Go, então nunca escreva código que dependa deles.
Como o append pode devolver outro array, você precisa usar o resultado dele:
append(s, 4) // compile error: append(s, 4) (value of type []int) is not used
s = append(s, 4) // correct
Acrescentar um slice inteiro usa a sintaxe de espalhamento: s = append(s, other...).
Fatiar compartilha o array subjacente
s[low:high] cria um novo cabeçalho de slice sobre o mesmo array, cobrindo os índices de low até high, sem incluir high. Qualquer um dos limites pode ser omitido. Nenhum elemento é copiado.
Saída:
[10 99 30 40 50]
3 4
Esse compartilhamento é o que torna o fatiamento barato. É também a origem do bug de slice mais famoso.
O bug de aliasing do append
Quando um sub-slice tem capacidade sobrando, fazer append nele escreve no array do slice pai, por cima de elementos que o pai ainda usa.
Saída:
[1 2 100 4 5]
[1 2 100]
[cmd test] [cmd test]
a e b compartilham a posição do array logo depois de "cmd", então o segundo append sobrescreveu o primeiro. O código parece correto e funciona sempre que a capacidade por acaso está cheia, e é por isso que esse bug só aparece às vezes.
Duas correções:
- A expressão de slice completa
s[low:high:max]limita a capacidade amax-low. Sem capacidade sobrando, o próximoappendprecisa alocar.first := base[:2:2]deixa o primeiro exemplo seguro. - Copie explicitamente quando um slice for sobreviver à chamada ou receber appends de forma independente:
slices.Clone(prefix)ouappend([]string(nil), prefix...).
Uma armadilha parecida: um slice pequeno de um array enorme mantém o array inteiro vivo para o coletor de lixo. Se você lê um arquivo de 100 MB e guarda data[:10], os 100 MB continuam na memória. Clone a parte que vai guardar.
copy
A função embutida copy(dst, src) copia min(len(dst), len(src)) elementos e devolve essa contagem. Ela nunca aumenta dst.
O copy lida corretamente com origem e destino sobrepostos, então copy(s[1:], s) desloca os elementos para a direita sem corromper nada.
O pacote slices
Desde o Go 1.21, o pacote padrão slices tem funções genéricas para as operações que você antes escrevia à mão.
| Função | O que faz |
|---|---|
Contains, Index | encontra um valor (ContainsFunc e IndexFunc recebem um predicado) |
Sort, SortFunc, SortStableFunc | ordena no lugar |
BinarySearch | busca em um slice ordenado |
Insert, Delete, DeleteFunc | insere ou remove e devolve o slice novo |
Compact | remove duplicados consecutivos |
Equal, Compare | compara elemento por elemento |
Clone, Reverse, Max, Min | o que o nome diz |
Collect, Sorted, Values, All | trabalham com iteradores (Go 1.23) |
Delete e Insert devolvem um slice que você precisa atribuir, como o append. Ordenação tem página própria: ordenação.
Remover elementos durante um laço por índice pula elementos. Use slices.DeleteFunc no lugar:
s = slices.DeleteFunc(s, func(n int) bool { return n%2 == 0 }) // drop evens
Slices 2D
Um slice de slices dá uma grade cujas linhas podem ter tamanhos diferentes. Cada linha precisa ser alocada separadamente.
Esquecer o make interno deixa cada linha nil, e grid[1][2] = 7 causa panic com index out of range [2] with length 0.
Slices nil e slices vazios
Um slice nil se comporta como um vazio para len, cap, range e append, então prefira var s []T como estado inicial. Inicialize com []T{} só quando a diferença importar, o que acontece principalmente na saída JSON (null ou []). Verifique se está vazio com len(s) == 0, não com s == nil.
Slices e funções
Uma função que recebe um slice pode mudar os elementos dele, e quem chama vê as mudanças. Ela não pode mudar o tamanho do slice de quem chama, porque recebeu uma cópia do cabeçalho. Uma função que faz append precisa devolver o slice novo:
func addAll(s []int, vals ...int) []int {
return append(s, vals...)
}
É por isso que append, slices.Delete e slices.Insert devolvem um slice.
Erros comuns
- Ignorar o resultado do
append. Descartá-lo por completo não compila, mas atribuirappend(s, x)a uma variável diferente da que você continua usando é um bug de lógica. - Dois appends a partir da mesma base. Eles podem compartilhar o armazenamento. Clone a base ou use uma expressão de slice completa.
make([]T, n)seguido deappend. Deixa n zeros no início.- Índice fora da faixa.
s[len(s)]causa panic. O último elemento és[len(s)-1]. - Comparar slices com
==. Sós == nilcompila. Useslices.Equal.
Perguntas frequentes
Qual a diferença entre tamanho e capacidade de um slice em Go?
len(s) é quantos elementos o slice guarda no momento. cap(s) é quantos elementos cabem no array subjacente a partir do primeiro elemento do slice. O append escreve na capacidade sobrando quando existe alguma, e aloca um array novo e maior quando não existe.
Como fazer append em um slice em Go?
Chame a função embutida append e atribua o resultado de volta: s = append(s, x). Ela pode acrescentar vários valores (append(s, 1, 2, 3)) ou outro slice (append(s, other...)). Sempre use o valor de retorno, porque o append pode devolver um slice que aponta para um array novo.
Como remover um elemento de um slice em Go?
Use slices.Delete(s, i, i+1) (Go 1.21), que desloca os elementos seguintes para trás e devolve o slice mais curto. Desde o Go 1.22 ele também zera as posições liberadas no final. Se a ordem não importar, troque com o último elemento e corte: s[i] = s[len(s)-1]; s = s[:len(s)-1].
Como verificar se um slice contém um valor em Go?
Use slices.Contains(s, v) do pacote padrão slices (Go 1.21). slices.Index(s, v) devolve a posição ou -1, e slices.ContainsFunc recebe um predicado. Para buscas repetidas em dados grandes, monte um map.
Qual a diferença entre um slice nil e um slice vazio em Go?
var s []int é nil; s := []int{} é vazio, mas não é nil. Os dois têm tamanho 0 e funcionam com len, range e append. A diferença aparece em verificações s == nil e no encoding/json, que codifica um slice nil como null e um slice vazio como [].