Erick Takeshi
Posted on January 5, 2024
Segue aqui anotações do capítulo 3 do livro Learning Go - Composite types.
Vou só deixar um único disclaimer aqui. As anotações são mais focadas em tópicos que julgo diferentes ou importantes com relação aos comportamentos específicos de Go. Também não quero focar em sintaxe ou coisas do gênero, afinal isso é mais memória muscular do que entendimento, sou a prova viva disso, programo em Ruby há mais de 9 anos e me vejo procurando sobre sintaxe de coisas básicas como switch case, splat operator e outras coisas que sabemos que existem mas usamos com menos frequência, então não fica decorado no nosso stack memory hehe.
Bora lá?
Arrays
Arrays são muito restritos em Go para serem utilizados com frequência, na maioria dos casos não os utilizamos.
Em um array, todos os seus elementos precisam ter o tipo especificado.
O tamanho de um array é considerado como parte do seu tipo, o que implica em não ser possível utilizar variável para declarar o tamanho de um array (o tamanho precisa ser conhecido em tempo de compilação).
Não é possível converter arrays de diferentes tamanhos entre si.
Slices
Slices são basicamente idênticos a arrays porém seu tamanho não faz parte do seu tipo, o que torna uma estrutura de dados muito mais flexível e útil.
Funções built-in notáveis para slices (e arrays)
- len (devolve o tamanho)
- append (adiciona elementos)
- cap (retorna a capacity)
- make (cria um slice do tamanho e capacity desejados)
- copy (copia um slice para outro)
Capacity
Representa a quantidade de elementos que “cabem” dentro do slice, ou seja, quantidade de blocos de memória que foram alocados para esse slice.
Quando a capacity estiver terminando, o runtime do Go aloca mais memória pra esse slice ao chamarmos a função built-in append
.
Quando temos uma estimativa de quão grande um slice pode ser é mais performático criar um slice já com a capacity correta, cada vez que o runtime precisa aumentar a capacity ele gera mais “lixo” pro garbage collector e também gasta tempo de processamento copiando e gerando um novo slice.
Declarando slices
Regras em ordem de prioridade:
- Se uma função pode retornar um slice vazio, declare usando o nil
var slice []type
- Se tiver alguns valores iniciais ou os valores não irão mudar, use um slice literal
data := []type{value1, value2}
- Para os outros casos use
make
e tente ser o mais preciso com relação ao capacity (com o tamanho 0 pode ser menos performáticos porém tem menos chance de introduzir bugs)
“Cortando” slices
É possível criar slices a partir de slices com a notação [:]
, inclusivo no start e exclusivo no end.
Os sub-slices acabam não sendo cópias dos slices geradores, ou seja, são duas variáveis que estão dividindo o mesmo espaço de memória, então mudar um slice pode acabar afetando o outro.
Slices independentes com copy
Caso seja necessário criar slices a partir de um slice e não queremos o comportamento de compartilhar memória, podemos usar a função copy
que vai gerar uma cópia totalmente independente de um slice.
Strings, Runes e Bytes
Em Go strings são sequências de bytes, ou seja, sequência de números. Esses bytes não necessitam estar em nenhum encoding especifico, porém, muitas libs assumem que a string está composta de uma sequência de UTF-8 code points.
Como com slices e arrays, podemos acessar bytes em strings usando a expressão de índice
Precisamos sempre lembrar que com expressões de índice estamos retornando bytes, então quando temos code points que utilizam mais de 1 byte pra representar seu caractere, o mapeamento da string não vai ser 1 pra 1.
Por exemplo, o emoji de sol 🌞, utiliza 4 bytes na sua representação UTF-8, então os 4 últimos bytes da string “Hello 🌞” se referem ao mesmo caractere.
Quando queremos manipular strings é sempre interessante usar as funções da standard library do Go, como as encontradas nos pacotes strings
e unicode/utf8
.
Maps
Um mapa é uma estrutura de dados muito comum e Go possui suporte nativo a ela.
O valor zero para mapas é nil
.
Um mapa nil tem length 0, tentar ler um mapa nil sempre vai retornar o valor zero do tipo dos valores, tentar escrever em um mapa nil causa um panic
e a execução do programa e terminada.
Caso queiramos criar um mapa vazio e escrever posteriormente nele podemos criar um mapa literal vazio.
totalWins := map[string]int{}
A função make funciona com mapas e permite especificar a capacidade desejada dele, que é bem útil quando já sabemos de antemão o tamanho que queremos deixar o mapa, pois assim como com slices, mapas podem crescer indefinidamente conforme adicionamos mais elementos, porém, ele precisa realocar a memória ocupada por um mapa.
Mapas não são comparáveis com o ==
ou !=
, podemos somente comparar com nil
.
O comma ok idiom
Quando estivermos lendo de um mapa, podemos usar o comma ok idiom para diferenciar se um valor zero lido do mapa é porque realmente ele está presente no mapa ou se estamos lendo um valor zero obtido de uma chave que não está presente.
m := map[string]int{ "hello": 5, "world": 0, }
// 5, true
v, ok := m["hello"]
// 0, true
v, ok = m["world"]
// 0, false
v, ok = m["goodbye"]
Deletando valores de um mapa
Podemos utilizar a função built-in delete
para deletar valores de um mapa.
Structs
Dica simples, sempre utilizar a forma de inicialização com o nome dos fields do struct, assim deixamos o código mais protegido para alterações futuras.
Anonymous structs são úteis quando queremos “traduzir” um tipo de dado para outro, como em json → structs ou vice e versa.
Structs são comparáveis se os tipos dos fields que os compõem são comparáveis (por exemplo não possuem slices ou maps nos fields).
Structs são conversíveis em outros structs desde que seus fields possuam os mesmos nomes, ordem e tipos.
Outros capítulos
Esta é uma série onde pretendo fazer anotações de todos os capítulos relevantes do livro Learning Go, seguem os links para os capítulos anteriores e futuros:
Posted on January 5, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.