Como não inicializar slices

Problema da inicialização sem capacidade

Inicializar um slice utilizando make sem determinar o tamanho e a capacidade não é nada performático.

Vamos considerar um exemplo de função que converte um slice de Foo para um slice de Bar:

func convert(foos []Foo) []Bar {
    bars := make([]Bar, 0) // Omitindo a capacidade

    for _, foo := range foos {
        bars = append(bars, fooToBar(foo))
    }

    return bars
}

Quando é adicionado o primeiro elemento no slice a partir do append, é alocado um array adjacente a ele com capacidade 1.

Adicionando o segundo elemento, a capacidade do array adjacente é estourada e cria-se um novo array adjacente com o dobro da capacidade do primeiro, como explicado em: https://arturbaccarin.dev.br/capacidade-dos-slices-explicada/.

Agora imagine se forem adicionados 1000 elementos ao slice. Isso fará com que sejam criados vários arrays adjacentes durante o processo, gerando uma carga extra ao Garbage Collector para limpar da memória os arrays não utilizados.


Primeira solução: definir a capacidade esperada

Uma das opções para reduzir a criação de arrays adjacentes e reutilizar o mesmo array é definir o slice já com a capacidade esperada:

func convert(foos []Foo) []Bar {
    bars := make([]Bar, 0, len(foos))

    for _, foo := range foos {
        bars = append(bars, fooToBar(foo))
    }

    return bars
}


Segunda solução: definir o comprimento esperado

A segunda opção é definir o slice com o comprimento esperado, inicializando seus elementos e substituindo os valores diretamente por referência ao índice:

func convert(foos []Foo) []Bar {
    bars := make([]Bar, len(foos))

    for i, foo := range foos {
        bars[i] = fooToBar(foo)
    }

    return bars
}

Essa abordagem é levemente mais performática que a primeira, pois a adição de novos valores ao slice não utiliza a função append.

No entanto, isso não justifica a utilização dessa opção em todos os casos. Aqui, a prioridade deve ser a legibilidade do código.


Slices e condições

Quando trabalhamos com slices em Go cujo tamanho futuro não é conhecido com precisão, como em casos em que elementos só são adicionados sob determinadas condições, surge a dúvida sobre como inicializar a estrutura de forma mais eficiente.

  • Se a condição for atendida na maioria das vezes, pode ser vantajoso reservar capacidade antecipadamente para reduzir realocações e otimizar o desempenho.
  • Por outro lado, quando a ocorrência é rara ou imprevisível, iniciar com um slice vazio evita desperdício de memória.

Não existe uma regra absoluta, mas sim a necessidade de avaliar o comportamento esperado do programa e escolher a estratégia que melhor se adapta ao caso de uso.


Em resumo, a escolha da forma de inicializar slices em Go deve equilibrar performance e clareza. Avalie sempre o padrão de uso esperado para decidir entre otimização de memória ou simplicidade de código.


Referência: HARSANYI, Teiva. 100 Go mistakes and how to avoid them. Shelter Island: Manning, 2022.