DEV Community

Renato O da Silva
Renato O da Silva

Posted on

Como usar const e iota em Go

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
Enter fullscreen mode Exit fullscreen mode

Quando você tem várias constantes relacionadas, agrupar em bloco fica mais limpo:

const (
    StatusOK      = 200
    StatusCriado  = 201
    StatusErro    = 500
)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
)
Enter fullscreen mode Exit fullscreen mode

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
)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
)
Enter fullscreen mode Exit fullscreen mode

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
)
Enter fullscreen mode Exit fullscreen mode

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
)
Enter fullscreen mode Exit fullscreen mode

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!
)
Enter fullscreen mode Exit fullscreen mode

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
)
Enter fullscreen mode Exit fullscreen mode

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
)
Enter fullscreen mode Exit fullscreen mode

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")
}
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)