Constantes e iota
Esta aula aborda o uso de constantes em Go, incluindo a declaração com `const`, o gerador de valores `iota` para criar enums de forma idiomática, e a diferença entre constantes tipadas e não tipadas. Exemplos práticos e exercícios consolidam o aprendizado.
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
iotapara sequências, mas lembre-se de que ele começa em 0; se precisar começar em 1, useiota + 1ou ignore o primeiro valor com_.
Referências
- Go Specification: Constants
- Go Specification: Iota
- Go Blog: Constants
- Go Code Review Comments: Constants
- Go Constraints Package
Exercícios
Crie um bloco de constantes para representar os meses do ano (Janeiro a Dezembro) usando iota, com um tipo personalizado
Mes. Implemente o métodoString()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] }Declare uma constante tipada
VelocidadeLuzdo tipointcom valor 299792458 (metros por segundo). Em seguida, tente atribuir essa constante a uma variável do tipofloat64sem 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) }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) }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 float64Crie 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 }