Nesta aula, vamos explorar um dos pilares da programação em Go: as constantes. Diferente de variáveis, constantes são valores imutáveis definidos em tempo de compilação, o que traz segurança e eficiência ao código. Vamos aprender como declarar constantes de forma simples, como usar o identificador iota para criar sequências de valores constantes (muito útil para enums) e entender a sutil diferença entre constantes tipadas e não tipadas. Ao final, você será capaz de utilizar constantes para tornar seu código mais expressivo e seguro.

Constantes em Go são declaradas com a palavra-chave const e podem ser de qualquer tipo básico (números, strings, booleanos). Elas não podem ser alteradas durante a execução do programa, o que as torna ideais para valores fixos como configurações, limites ou códigos de erro. Além disso, Go oferece um mecanismo elegante para criar enums através do iota, um contador que gera valores sequenciais automaticamente.

const

A declaração de constantes em Go é feita com a palavra-chave const, seguida do nome da constante, opcionalmente o tipo, e o valor. Diferente de variáveis, constantes devem ter seu valor conhecido em tempo de compilação. Elas podem ser declaradas individualmente ou em blocos, o que melhora a legibilidade quando várias constantes relacionadas são definidas juntas.

Exemplo básico:

package main

import "fmt"

const Pi = 3.14159
const (
    StatusOK = 200
    StatusNotFound = 404
)

func main() {
    fmt.Println(Pi)
    fmt.Println(StatusOK)
}

No código acima, Pi é uma constante não tipada (seu tipo será inferido quando usada), enquanto StatusOK e StatusNotFound são constantes não tipadas do tipo inteiro. Note que não usamos := para constantes, e sim =. Constantes não podem ser declaradas com :=.

É importante lembrar que constantes só podem conter valores que o compilador pode determinar, como literais, expressões aritméticas simples ou chamadas de funções built-in como len() aplicadas a strings constantes. Funções definidas pelo usuário não podem ser usadas.

iota e enums idiomáticos

O iota é um identificador pré-declarado em Go que representa um contador inteiro que começa em 0 e é incrementado a cada nova linha dentro de um bloco const. Ele é reiniciado a cada novo bloco const e pode ser usado para criar sequências de valores constantes de forma concisa, especialmente para enums.

Exemplo básico de iota:

package main

import "fmt"

const (
    Segunda = iota // 0
    Terca          // 1
    Quarta         // 2
    Quinta         // 3
    Sexta          // 4
    Sabado         // 5
    Domingo        // 6
)

func main() {
    fmt.Println(Segunda, Terca, Quarta)
}

Saída: 0 1 2. O iota é incrementado automaticamente. Você também pode ignorar valores ou usar expressões com iota. Por exemplo, para criar flags binárias:

const (
    Read = 1 << iota // 1
    Write              // 2
    Execute            // 4
)

func main() {
    fmt.Println(Read, Write, Execute) // 1 2 4
}

Em Go, não existe uma palavra-chave enum como em outras linguagens. A forma idiomática de criar enums é usar constantes com iota, muitas vezes combinadas com um tipo personalizado para garantir type safety. Por exemplo:

type DiaSemana int

const (
    Domingo DiaSemana = iota
    Segunda
    Terca
    Quarta
    Quinta
    Sexta
    Sabado
)

func (d DiaSemana) String() string {
    return [...]string{"Domingo", "Segunda", "Terca", "Quarta", "Quinta", "Sexta", "Sabado"}[d]
}

func main() {
    fmt.Println(Sabado) // 6, mas com String() retorna "Sabado"
}

Esse padrão é amplamente utilizado em código Go profissional.

Constantes tipadas e não tipadas

Em Go, constantes podem ser tipadas ou não tipadas. Constantes não tipadas têm um tipo implícito (como inteiro, float, string) mas não estão vinculadas a um tipo específico, o que permite maior flexibilidade em expressões. Por exemplo, uma constante não tipada pode ser usada tanto em contextos que esperam int quanto float64, desde que o valor seja compatível.

Exemplo:

const x = 42       // não tipada
const y int = 42   // tipada

var a int = x      // ok, x é usado como int
var b float64 = x  // ok, x é usado como float64
// var c int = y   // ok
// var d float64 = y // erro: y é int, não pode converter implicitamente

Constantes não tipadas são mais flexíveis, enquanto constantes tipadas oferecem segurança de tipo. A escolha depende do contexto: para valores que serão usados em múltiplos contextos numéricos, prefira não tipadas; para enums com tipo personalizado, use tipadas.

Outro ponto importante: constantes não tipadas só existem em tempo de compilação; quando são usadas em uma expressão, o compilador infere o tipo necessário. Isso permite que expressões com constantes não tipadas sejam avaliadas com precisão arbitrária, evitando erros de overflow em tempo de compilação.

Exemplos

Vamos consolidar com alguns exemplos práticos que combinam os conceitos.

Exemplo 1: Constantes para configuração

package main

import "fmt"

const (
    MaxUsers = 1000
    MinAge   = 18
    AppName  = "MeuApp"
)

func main() {
    fmt.Printf("App: %s, Max Users: %d, Min Age: %d\n", AppName, MaxUsers, MinAge)
}

Exemplo 2: Enum com iota e tipo personalizado

package main

import "fmt"

type Status int

const (
    Inactive Status = iota
    Active
    Suspended
    Deleted
)

func (s Status) String() string {
    switch s {
    case Inactive:
        return "Inactive"
    case Active:
        return "Active"
    case Suspended:
        return "Suspended"
    case Deleted:
        return "Deleted"
    default:
        return "Unknown"
    }
}

func main() {
    fmt.Println(Active) // 1, mas com String() retorna "Active"
}

Exemplo 3: Constantes tipadas para evitar erros

package main

import "fmt"

type ByteSize float64

const (
    _           = iota             // ignora primeiro valor
    KB ByteSize = 1 << (10 * iota) // 1 << (10*1) = 1024
    MB                             // 1 << (10*2) = 1048576
    GB                             // 1 << (10*3) = 1073741824
)

func main() {
    fmt.Println(KB, MB, GB)
}

Esses exemplos mostram como constantes e iota podem ser usados para criar código limpo e expressivo.

Boas práticas

  • Use constantes para todos os valores mágicos no código (números, strings) para melhorar a legibilidade e manutenção.
  • Preferir constantes não tipadas para valores numéricos genéricos, a menos que você precise de segurança de tipo.
  • Ao criar enums, sempre defina um tipo personalizado para evitar que valores sejam misturados com inteiros comuns.
  • Implemente o método String() para enums para obter representações legíveis.
  • Use iota para sequências, mas lembre-se de que ele começa em 0; se precisar começar em 1, use iota + 1 ou ignore o primeiro valor com _.

Referências

Exercícios

  1. Crie um bloco de constantes para representar os meses do ano (Janeiro a Dezembro) usando iota, com um tipo personalizado Mes. Implemente o método String() que retorna o nome por extenso.

    ✓ Resposta:
    type Mes int
    
    const (
        Janeiro Mes = iota + 1
        Fevereiro
        Marco
        Abril
        Maio
        Junho
        Julho
        Agosto
        Setembro
        Outubro
        Novembro
        Dezembro
    )
    
    func (m Mes) String() string {
        meses := [...]string{"Janeiro", "Fevereiro", "Marco", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"}
        if m < 1 || m > 12 {
            return "Mês inválido"
        }
        return meses[m-1]
    }
  2. Declare uma constante tipada VelocidadeLuz do tipo int com valor 299792458 (metros por segundo). Em seguida, tente atribuir essa constante a uma variável do tipo float64 sem conversão explícita. O que acontece? Escreva um pequeno programa para testar.

    ✓ Resposta:

    Não compila, pois constantes tipadas não podem ser atribuídas a variáveis de tipo diferente sem conversão explícita. O código abaixo gera erro:

    package main
    
    import "fmt"
    
    const VelocidadeLuz int = 299792458
    
    func main() {
        var v float64 = VelocidadeLuz // erro: cannot use VelocidadeLuz (type int) as type float64 in assignment
        fmt.Println(v)
    }
  3. Use iota para criar constantes que representem permissões de arquivo: Read (1), Write (2), Execute (4), ReadWrite (Read|Write), ReadExecute (Read|Execute), All (Read|Write|Execute). Dica: use operadores bitwise.

    ✓ Resposta:
    type Permissao int
    
    const (
        Read Permissao = 1 << iota // 1
        Write                        // 2
        Execute                      // 4
        ReadWrite = Read | Write
        ReadExecute = Read | Execute
        All = Read | Write | Execute
    )
    
    func main() {
        fmt.Println(Read, Write, Execute, ReadWrite, ReadExecute, All)
    }
  4. Explique a diferença entre constantes tipadas e não tipadas em Go, dando um exemplo onde a diferença é relevante.

    ✓ Resposta:

    Constantes não tipadas não possuem um tipo fixo até serem usadas, permitindo que sejam atribuídas a variáveis de tipos compatíveis sem conversão explícita. Já constantes tipadas têm um tipo específico e só podem ser usadas em contextos que esperam aquele tipo. Exemplo:

    const a = 100       // não tipada
    const b int = 100   // tipada
    
    var x int = a       // ok
    var y float64 = a   // ok
    // var z float64 = b // erro: cannot use b (type int) as type float64
    
  5. Crie um programa que defina constantes para os dias da semana usando iota, mas começando em 1 (Segunda=1, ..., Domingo=7). Imprima o valor de Quarta-feira.

    ✓ Resposta:
    package main
    
    import "fmt"
    
    const (
        Segunda = iota + 1
        Terca
        Quarta
        Quinta
        Sexta
        Sabado
        Domingo
    )
    
    func main() {
        fmt.Println(Quarta) // 3
    }