Inspeção Dinâmica com Reflect
O pacote nativo reflect possibilita a inspeção e modificação de estruturas durante a execução do programa. Isso é particularmente útil quando se necessita acessar campos de uma struct utilizando strings como identificadores, ou quando o tipo exato da variável só é conhecido em tempo de execução.
Dentro deste pacote, destacam-se dois componentes fundamentais: reflect.Type, que descreve o tipo da variável, e reflect.Value, que contém o valor atual. Para instanciá-los, utilizam-se as funções reflect.TypeOf e reflect.ValueOf, respectivamente.
Obtenção de Tipos e Valores
O exemplo abaixo demonstra como recuperar informações básicas sobre uma variável simples:
package main
import (
"fmt"
"reflect"
)
func main() {
variable := 42
fmt.Println(reflect.TypeOf(variable)) // int
fmt.Println(reflect.ValueOf(variable)) // 42
}
Acessando Campos de Structs
Para estruturas mais complexas, é possível iterar sobre os campos ou acessá-los diretamente pelo nome ou índice. Note que para modificar valores, os campos devem ser exportados (iniciados com letra maiúscula).
package main
import (
"fmt"
"reflect"
)
type Usuario struct {
Nome string
Cidade string
}
func main() {
dados := Usuario{"Carlos", "Lisboa"}
v := reflect.ValueOf(dados)
// Total de campos
fmt.Println(v.NumField())
// Acesso via índice
fmt.Println(v.Field(0).String())
// Acesso via nome
campo := "Cidade"
fmt.Println(v.FieldByName(campo).String())
}
Modificação de Valores via Ponteiro
A alteração de valores exige que se passe o endereço da struct para o reflexo. O método Elem() é utilizado para dereferenciar o ponteiro. Além disso, o método CanSet() verifica se a modificação é permitida.
package main
import (
"fmt"
"reflect"
)
type Perfil struct {
Titulo string
Pais string
}
func main() {
target := "Titulo"
p := &Perfil{}
v := reflect.ValueOf(p).Elem()
// Verifica permissão de escrita
fmt.Println(v.FieldByName(target).CanSet())
// Atribuição de novos valores
v.FieldByName(target).SetString("Engenheiro")
v.FieldByName("Pais").SetString("Brasil")
fmt.Println(p) // &{Engenheiro Brasil}
}
Uso de Tags em Structs
Tags são metadados anexados aos campos da struct, frequentemente usados para serialização. Elas podem ser lidas através do tipo refletido.
package main
import (
"fmt"
"reflect"
)
type Dados struct {
ID int `json:"identificador"`
}
func main() {
t := reflect.TypeOf(Dados{})
field, _ := t.FieldByName("ID")
fmt.Println(field.Tag) // json:"identificador"
fmt.Println(field.Tag.Get("json")) // identificador
}
Registro de Eventos (Logging)
O pacote log oferece funcionalidades básicas para registro de saída. As principais funções incluem Print para informação padrão, Panic que registra e entra em pânico, e Fatal que registra e encerra o processo imediatamente. Por padrão, as mensagens incluem carimbos de tempo.
É possível configurar a saída para arquivos personalizados com prefixos específicos:
package main
import (
"log"
"os"
)
func main() {
arquivo, _ := os.OpenFile("./registro.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
defer arquivo.Close()
logger := log.New(arquivo, "[APP]", log.Ldate|log.Ltime)
logger.Println("Sistema iniciado com sucesso")
}
Controle de Tempo e Pausas
O pacote time gerencia a execução temporal. A função Sleep bloqueia a goroutine atual por uma duração específica. Já a função AfterFunc agenda uma execução futura, mas requer que o programa principle permaneça ativo até que o tempo expire.
Pausa na Execução
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("Inicio")
time.Sleep(2 * time.Second)
fmt.Println("Fim após pausa")
}
Execução Diferida
Ao usar timers assíncronos, é crucial garantir que a thread principal não termine prematuramente:
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("Agendando tarefa")
time.AfterFunc(3*time.Second, func() {
fmt.Println("Tarefa executada")
})
// Bloqueio necessário para aguardar o timer
time.Sleep(4 * time.Second)
fmt.Println("Encerrando")
}