VOOZH about

URL: https://dev.to/oliversieto/como-usar-const-e-iota-em-go-h3l

⇱ Como usar const e iota em Go - DEV Community


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")
}

Rodar no Go Playground

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.

Referências