M01·02Conceitos Fundamentais

CAPÍTULO 02

Conceitos Fundamentais

Variáveis, tipos, structs, interfaces e funções — os blocos de construção de todo programa Go.

Por Thiago Souza18 min de leituraAtualizado em 2026-05

Variáveis e tipos básicos

Em Go, há três jeitos de declarar variáveis. Parece confuso, mas faz sentido:

go
package main
 
import "fmt"
 
func main() {
    // JEITO 1: explícito e formal — você diz o tipo.
    // Útil quando você quer ser claro ou declarar sem inicializar.
    var name string = "Maria"
 
    // JEITO 2: o Go infere o tipo a partir do valor.
    // Mais limpo, mesma coisa do jeito 1.
    var age = 30
 
    // JEITO 3: o "atalho" — só funciona DENTRO de funções.
    // É o jeito mais usado no dia a dia.
    city := "São Paulo"
 
    fmt.Println(name, age, city)
}

Regra de ouro: dentro de funções, use :=. Fora delas (variáveis globais), use var.

Tipos primitivos que você precisa conhecer

go
// Números inteiros (com sinal)
var a int    = 42       // tamanho depende da plataforma (32 ou 64 bits)
var b int32  = 42       // exatamente 32 bits
var c int64  = 42       // exatamente 64 bits
 
// Números inteiros (sem sinal — só positivos)
var d uint   = 42
 
// Números decimais
var price float64 = 19.99
 
// Texto
var msg string = "olá"
 
// Booleano
var active bool = true
 
// Byte é apelido para uint8 — usado para dados binários
var b1 byte = 'A'  // 65
 
// Rune é apelido para int32 — representa um caractere Unicode
var letter rune = 'á'
Por que rune em vez de char? Porque o mundo não é só ASCII. Um rune representa um code point Unicode inteiro, então emojis e caracteres acentuados funcionam direito.

Constantes

go
const Pi = 3.14159
const AppName = "MeuBackend"
 
// Bloco de constantes (mais elegante)
const (
    StatusActive  = "ATIVO"
    StatusInactive = "INATIVO"
    StatusBanned  = "BANIDO"
)

Sintaxe básica: o essencial

Condicionais (if)

go
age := 18
 
if age >= 18 {
    fmt.Println("Maior de idade")
} else if age >= 13 {
    fmt.Println("Adolescente")
} else {
    fmt.Println("Criança")
}
 
// Truque idiomático: declarar variável dentro do if.
// "err" só existe dentro deste bloco — escopo limpo!
if err := doSomething(); err != nil {
    fmt.Println("Deu erro:", err)
}
Em Go, não existem parênteses ao redor da condição, mas as chaves {} são obrigatórias, mesmo para uma única linha. Isso evita aquele bug clássico do C onde você esquece a chave.

Loops (for)

Em Go existe apenas um tipo de loop: o for. Mas ele tem várias formas:

go
// Forma 1: clássica (igual C, Java, etc)
for i := 0; i < 5; i++ {
    fmt.Println(i)
}
 
// Forma 2: como "while" de outras linguagens
counter := 0
for counter < 5 {
    counter++
}
 
// Forma 3: loop infinito (cuidado!)
for {
    fmt.Println("rodando para sempre...")
    break // sai do loop
}
 
// Forma 4: range — o queridinho do Go
names := []string{"Ana", "Bia", "Caio"}
for index, name := range names {
    fmt.Printf("Posição %d: %s\n", index, name)
}
 
// Quando você não precisa do índice, use "_" (underscore)
for _, name := range names {
    fmt.Println(name)
}

Switch

O switch em Go é mais poderoso que em outras linguagens:

go
day := "segunda"
 
switch day {
case "segunda", "terça", "quarta", "quinta", "sexta":
    fmt.Println("Dia útil")
case "sábado", "domingo":
    fmt.Println("Fim de semana")
default:
    fmt.Println("Dia inválido")
}
 
// Switch sem expressão funciona como if/else if encadeado
age := 25
switch {
case age < 13:
    fmt.Println("Criança")
case age < 18:
    fmt.Println("Adolescente")
default:
    fmt.Println("Adulto")
}
Em Go, o switch não cai automaticamente para o próximo caso (não tem aquele break chato que você esquece em outras linguagens). Se quiser cair, use fallthrough.

Arrays, Slices e Maps

Arrays (tamanho FIXO)

go
// Array de 3 inteiros — tamanho FAZ PARTE do tipo
var nums [3]int = [3]int{10, 20, 30}
fmt.Println(nums[0]) // 10

Na prática, quase ninguém usa arrays diretamente. Use slices.

Slices (a estrela do show)

Um slice é uma "janela" sobre um array. Tamanho dinâmico, fácil de usar:

go
// Criando um slice (note: SEM tamanho entre os colchetes)
names := []string{"Ana", "Bia"}
 
// Adicionando elementos com append
names = append(names, "Caio")
names = append(names, "Diana", "Eva") // pode adicionar vários
 
fmt.Println(names)       // [Ana Bia Caio Diana Eva]
fmt.Println(len(names))  // 5  — quantidade de elementos
 
// Pegando um pedaço (slicing)
first := names[0:2]   // [Ana Bia]   — do índice 0 até o 2 (exclusive)
last  := names[2:]    // [Caio Diana Eva]
Um slice é como uma régua de ler livro. Você aponta pra um trecho e move conforme precisa. Não copia o livro inteiro.

Maps (dicionários / hash tables)

go
// Mapa de string para int
ages := map[string]int{
    "Ana": 30,
    "Bia": 25,
}
 
// Adicionar / atualizar
ages["Caio"] = 40
 
// Ler com verificação (padrão idiomático!)
age, exists := ages["Diana"]
if exists {
    fmt.Println("Diana tem", age, "anos")
} else {
    fmt.Println("Diana não existe no mapa")
}
 
// Remover
delete(ages, "Ana")
 
// Iterar
for name, age := range ages {
    fmt.Printf("%s tem %d anos\n", name, age)
}
A ordem de iteração de um map em Go é aleatória de propósito! Não dependa dela. Se precisar de ordem, use slices.

Structs: agrupando dados

Uma struct é como um molde para criar objetos com vários campos. Pense numa ficha cadastral:

go
package main
 
import "fmt"
 
// Definimos a "forma" de um Usuário
type User struct {
    ID       int
    Name     string
    Email    string
    Active   bool
}
 
func main() {
    // Jeito 1: criar nomeando os campos (RECOMENDADO)
    u1 := User{
        ID:     1,
        Name:   "Maria Silva",
        Email:  "maria@email.com",
        Active: true,
    }
 
    // Jeito 2: criar pela ordem (frágil — não use em produção)
    u2 := User{2, "João Souza", "joao@email.com", true}
 
    // Acessar campos com ponto
    fmt.Println(u1.Name) // Maria Silva
 
    // Modificar
    u1.Email = "maria.nova@email.com"
 
    fmt.Println(u1, u2)
}

Structs aninhadas

go
type Address struct {
    Street string
    City   string
    State  string
}
 
type Customer struct {
    Name    string
    Address Address // struct dentro de struct
}
 
c := Customer{
    Name: "Ana",
    Address: Address{
        Street: "Rua das Flores, 123",
        City:   "Curitiba",
        State:  "PR",
    },
}
 
fmt.Println(c.Address.City) // Curitiba

Funções

Funções em Go têm uma sintaxe peculiar mas muito clara:

go
// func NOME(parâmetros) tipoDeRetorno { ... }
 
func add(a int, b int) int {
    return a + b
}
 
// Quando dois parâmetros têm o mesmo tipo, pode encurtar:
func subtract(a, b int) int {
    return a - b
}
 
// MÚLTIPLOS RETORNOS — uma das características mais legais do Go!
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("divisão por zero")
    }
    return a / b, nil
}
 
func main() {
    // Chamando uma função que retorna múltiplos valores
    result, err := divide(10, 2)
    if err != nil {
        fmt.Println("Erro:", err)
        return
    }
    fmt.Println("Resultado:", result)
}
Por que múltiplos retornos importam? Esse é o coração do error handling em Go. Em vez de exceções (try/catch), Go retorna o erro como um segundo valor. Você É OBRIGADO a lidar com ele. Isso evita aquela cultura de "deixa estourar exception em produção".

Métodos: funções "presas" a um tipo

go
type Rectangle struct {
    Width, Height float64
}
 
// Esta função é um MÉTODO do Rectangle.
// "(r Rectangle)" é o "receiver" — diz a qual tipo o método pertence.
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}
 
// Receiver com PONTEIRO permite modificar o struct original
func (r *Rectangle) Double() {
    r.Width *= 2
    r.Height *= 2
}
 
func main() {
    rect := Rectangle{Width: 3, Height: 4}
    fmt.Println(rect.Area()) // 12
 
    rect.Double()
    fmt.Println(rect.Area()) // 48
}
Quando usar ponteiro * no receiver? Use se o método modifica o struct, ou se o struct é grande (evita cópias caras). Na dúvida, use ponteiro — é o padrão da maioria dos projetos sérios.

Interfaces: o conceito mais elegante de Go

Interface é a forma como Go faz polimorfismo. Mas calma, é muito mais simples do que parece.

A grande sacada

Em Java/C#, você diz: "Esta classe IMPLEMENTA esta interface" (com a palavra implements).

Em Go: "Se anda como pato e faz quack como pato, então é um pato." Não precisa declarar nada. Se um tipo tem os métodos da interface, ele automaticamente satisfaz a interface.

go
package main
 
import "fmt"
 
// Definimos uma interface: "qualquer coisa que sabe Falar"
type Speaker interface {
    Speak() string
}
 
// Dog tem o método Speak() — então é um Speaker
type Dog struct {
    Name string
}
 
func (c Dog) Speak() string {
    return c.Name + " diz: Au au!"
}
 
// Cat também tem o método Speak() — também é um Speaker
type Cat struct {
    Name string
}
 
func (g Cat) Speak() string {
    return g.Name + " diz: Miau!"
}
 
// Função que aceita QUALQUER coisa que seja Speaker
func makeSpeak(f Speaker) {
    fmt.Println(f.Speak())
}
 
func main() {
    rex := Dog{Name: "Rex"}
    mia := Cat{Name: "Mia"}
 
    makeSpeak(rex) // Rex diz: Au au!
    makeSpeak(mia) // Mia diz: Miau!
}
Você pode criar interfaces para tipos que já existem em bibliotecas que você não controla. Em Java isso seria impossível sem alterar o código original.

Analogia do mundo real

Pense numa tomada elétrica. A "interface" é o formato dos buracos. Não importa se é uma TV, geladeira ou liquidificador — se o plug encaixa, funciona. Você não precisa "registrar" sua TV como "implementadora da interface tomada".

Interface vazia: interface{} ou any

Como toda interface não exige nada, uma interface sem métodos aceita literalmente qualquer coisa:

go
// "any" é um apelido para interface{} adicionado no Go 1.18
func display(v any) {
    fmt.Println(v)
}
 
display(42)
display("texto")
display([]int{1, 2, 3})
Use com moderação. Quando você usa any, perde a segurança de tipos. Prefira interfaces específicas sempre que possível.