Toda linguagem tem um jeito de declarar valores fixos. Em Go esse jeito chama const, e junto dele vem um identificador especial chamado iota que muita gente acha estranho na primeira vez. Neste post vamos passar pelos fundamentos de const, entender o que iota faz, e ver um caso prático de uso com flags de permissão.
O que é uma constante em Go
Uma constante guarda um valor fixo, que o compilador já conhece antes do programa rodar. Por isso você não pode usar resultado de função nem variável dentro dela. Tudo precisa ser literal.
const Pi = 3.14159
const Saudacao = "olá"
const MaxTentativas int = 5
Quando você tem várias constantes relacionadas, agrupar em bloco fica mais limpo:
const (
StatusOK = 200
StatusCriado = 201
StatusErro = 500
)
Constantes tipadas e não tipadas
Em Go existem dois tipos de constante.
Constante tipada tem o tipo fixo. Se você quer usar em outro tipo, precisa converter na mão:
const x int = 10
var a int32 = x // ERRO
var b int32 = int32(x) // OK
Constante não tipada é mais flexível. Ela só ganha tipo no momento em que é usada, adaptando ao tipo que o código pediu:
const x = 10 // não tipada
var a int32 = x // OK, vira int32 aqui
var b float64 = x // OK, vira float64 aqui
Prefira criar constantes não tipadas quando puder. A reutilização fica mais ampla.
iota
iota é um identificador especial que só funciona dentro de um bloco const. O valor dele é o número da linha dentro do bloco, começando em zero. Toda vez que você escreve uma nova linha de constante no mesmo bloco, o iota soma um.
const (
A = iota // 0
B = iota // 1
C = iota // 2
)
Go simplifica o uso de iota, depois que você usa uma expressão na primeira linha, as linhas seguintes podem ficar vazias e ele repete a mesma expressão. O iota continua incrementando sozinho.
const (
A = iota // 0
B // 1
C // 2
)
Cada novo bloco const reinicia o iota para zero.
const (
A = iota // 0
B // 1
C // 2
)
const (
X = iota // 0 (reinicia)
Y // 1
Z // 2
)
const P = iota // 0 (cada const solto também reinicia)
const Q = iota // 0
Cada bloco const (...) ou declaração const solta tem seu próprio contador. iota só vive dentro do bloco onde aparece.
Você também pode pular valores usando _:
const (
A = iota // 0
_ // pula o 1
B // 2
C // 3
)
iota também serve para gerar progressões matemáticas. Um padrão clássico é declarar unidades de tamanho (KB, MB, GB...) usando bit shift:
const (
_ = iota // 0 (descartado)
KB = 1 << (10 * iota) // 1 << 10 = 1024
MB // 1 << 20 = 1048576
GB // 1 << 30
TB // 1 << 40
)
Como funciona: na linha do KB, iota vale 1, então 1 << (10 * 1) resulta em 1024. Nas linhas seguintes, Go repete a expressão automaticamente, e iota continua incrementando (2, 3, 4...).
Cuidados com o uso de iota
Um problema comum que gera bug silencioso é adicionar nova constante num bloco const que usa iota. Como iota gera valor por posição, inserir item no meio renumera tudo abaixo.
package main
type Status int
const (
StatusPending Status = iota // 0
StatusActive // 1
StatusInactive // 2
StatusDeleted // 3
)
No exemplo acima, o valor 2 corresponde a StatusInactive. Se o dev insere nova constante no meio, surge problema:
const (
StatusPending Status = iota // 0
StatusActive // 1
StatusBanned // 2 ← novo
StatusInactive // 3 ← era 2!
StatusDeleted // 4 ← era 3!
)
Bug silencioso: compila, testes unitários passam, mas registros persistidos com status=2 agora apontam para StatusBanned.
Exemplo antes de adicionar uma nova constante
Exemplo após adicionar uma nova constante
Ao usarmos iota com expressões, a expressão depende da posição de cada constante. Se alguém adiciona uma nova entrada no topo, todo o cálculo desloca:
const (
_ = iota // iota=0
B = iota // iota=1, B=1 ← linha nova
KB = 1 << (10 * iota) // iota=2, KB = 1<<20 = 1048576 ← era 1024!
MB // iota=3, MB = 1<<30 ← era 1<<20!
GB // iota=4
TB // iota=5
)
KB virou MB. MB virou GB. Cada unidade subiu uma ordem de grandeza. O código compila normalmente. Mas qualquer cálculo de memória, tamanho de arquivo ou limite de buffer passa a retornar valores absurdos.
Caso prático: flags de permissão com bitmask
Imagine que você precisa representar permissões de um usuário. Cada permissão é independente: ler, escrever, executar. Você quer combinar várias na mesma variável e depois testar quais estão ligadas.
A ideia é usar um bit para cada permissão. Pense em três interruptores numa fileira. Cada permissão liga um interruptor diferente.
Em vez de iota puro, usamos 1 << iota. O operador << desloca os bits para a esquerda. Então 1 << 0 vale 1, 1 << 1 vale 2, 1 << 2 vale 4. Cada linha do bloco ativa um bit diferente:
type Permissao int
const (
Ler Permissao = 1 << iota // 1 << 0 = 1 binário 001
Escrever // 1 << 1 = 2 binário 010
Executar // 1 << 2 = 4 binário 100
)
Agora cada permissão ocupa um bit único. Combinar e testar fica simples:
p := Ler | Escrever // 001 | 010 = 011 (vale 3)
if p&Ler != 0 {
fmt.Println("pode ler")
}
if p&Escrever != 0 {
fmt.Println("pode escrever")
}
if p&Executar != 0 {
fmt.Println("pode executar") // não imprime
}
p = p &^ Escrever // remove Escrever, sobra só Ler
if p&Escrever == 0 {
fmt.Println("não pode mais escrever")
}
Por que 1 << iota e não iota puro? Se fosse iota direto, os valores seriam 0, 1, 2, 3. Ao combinar com OR você teria colisão. Por exemplo 1 | 2 daria 3, que seria igual à terceira constante. Usando um bit único por flag cada permissão fica isolada e dá pra somar sem conflito.
Top comments (0)