Fundamentos de Programação/01 - Fundamentos Duros/Semana 01 - Diagnóstico e o que Acontece por Baixo5 minentregavel
Entregável - Por que a = b às vezes copia
Por que a = b às vezes copia e às vezes não
a = b sempre copia — em Go e em qualquer linguagem imperativa. A pergunta
errada é "copia ou não copia"; a pergunta certa é "copia o quê?".
O conceito, antes do Go
Atribuição copia a representação do valor. O que varia entre linguagens não é se há cópia — é o que cabe na representação:
- Se a representação é o dado inteiro (um inteiro, uma struct por valor), copiar dá independência.
- Se a representação é uma referência ao dado (um ponteiro, um handle), copiar dá alias: dois nomes, um dado.
Nenhuma linguagem mainstream copia profundamente por padrão, e isso não é omissão.
Cópia profunda é caríssima e muitas vezes indefinida: o que copiar de um socket aberto?
De um grafo com ciclo? De um mutex travado? Então cada linguagem escolhe, tipo por
tipo, o que entra na representação — e é essa escolha que decide se a = b isola ou
compartilha.
| Linguagem | A escolha que ela fez | Efeito de a = b |
|---|---|---|
| Java | objeto é sempre referência; primitivo é valor | objeto aliasa, primitivo copia — e nada no tipo avisa |
| Python | tudo é referência (imutáveis tornam o alias inofensivo) | sempre aliasa |
| C++ | você decide, e pode redefinir com construtor de cópia / operator= |
pode ser raso ou profundo |
| C | struct copia campo a campo; ponteiro aliasa | visível no tipo |
| Go | struct e array copiam; slice, map e chan contêm ponteiro | visível no tipo |
Então a pergunta "por que às vezes copia" é, no fundo: quais tipos da minha linguagem carregam referência dentro? Em Go a resposta é uma tabela curta — e é o resto desta página.
A regra única, em Go
Todo tipo em Go tem um tamanho fixo, conhecido em tempo de compilação. a = b copia
exatamente esses bytes — nem mais, nem menos. Não existe cópia profunda em Go,
nem na linguagem nem na biblioteca padrão.
E existe uma função que te diz literalmente quantos bytes a = b copia:
unsafe.Sizeof. Ela é a resposta da pergunta, não uma aproximação dela.
O que cabe em cada valor (amd64/arm64)
| Tipo | unsafe.Sizeof |
O que está dentro | a = b dá alias? |
|---|---|---|---|
bool |
1 | o valor | não |
int, int64, float64 |
8 | o valor | não |
*T |
8 | um endereço | sim |
string |
16 | ptr + len | não (imutável) |
[]T |
24 | ptr + len + cap | sim (do array de trás) |
map[K]V |
8 | ptr pro hmap | sim |
chan T |
8 | ptr pro hchan | sim |
func() |
8 | ptr pro closure | sim (das capturas) |
any / error |
16 | ptr de tipo + ptr de dado | depende do que tem dentro |
struct{A, B int} |
16 | os dois campos | não |
[3]int |
24 | os três elementos | não |
struct{} |
0 | nada | — |
Duas linhas dessa tabela explicam quase toda a confusão:
[3]intcopia 24 bytes de dados.[]intcopia 24 bytes de metadados. Mesmo tamanho, significados opostos.mapé 8 bytes. Não existe "copiar um mapa" em Go — você copia o ponteiro. Para copiar de verdade:maps.Clone.
De onde vem a sensação de que "às vezes não copia"
1. O valor contém ponteiro. Você copiou o ponteiro fielmente; os dois lados passam a olhar o mesmo dado. A cópia funcionou — ela só era rasa.
2. Array vs slice. [3]int é um valor; []int é uma janela para um array que
está em outro lugar.
3. Receiver de método. func (c Config) copia o receiver; func (c *Config)
não. A mesma chamada c.Metodo() tem semântica diferente conforme a assinatura.
4. append. Ele pode devolver um slice apontando pro mesmo array (se cap
sobra) ou pra um array novo (se realocou). O mesmo código é destrutivo ou inofensivo
dependendo de um número que você não vê na chamada.
O teste decisivo
"Esse tipo tem ponteiro dentro?"
Se sim, a = b te dá alias e você precisa clonar explicitamente para isolar.
Se não, a = b te dá independência total.
Programa que prova tudo
package main
import (
"fmt"
"maps"
"slices"
"unsafe"
)
type Config struct {
Nome string
Tags []string
}
func main() {
// 1. Sizeof é literalmente "quantos bytes a = b copia"
fmt.Println("int ", unsafe.Sizeof(int(0))) // 8
fmt.Println("string ", unsafe.Sizeof("")) // 16
fmt.Println("[]int ", unsafe.Sizeof([]int(nil))) // 24
fmt.Println("[3]int ", unsafe.Sizeof([3]int{})) // 24
fmt.Println("map ", unsafe.Sizeof(map[int]int(nil)))// 8
fmt.Println("any ", unsafe.Sizeof(any(nil))) // 16
// 2. Array copia dados; slice copia metadados
arr1 := [3]int{1, 2, 3}
arr2 := arr1
arr2[0] = 99
fmt.Println(arr1, arr2) // [1 2 3] [99 2 3] <- independentes
sl1 := []int{1, 2, 3}
sl2 := sl1
sl2[0] = 99
fmt.Println(sl1, sl2) // [99 2 3] [99 2 3] <- MESMO array
// 3. Struct: cópia rasa. Escalar isola, ponteiro não.
c1 := Config{Nome: "prod", Tags: []string{"a", "b"}}
c2 := c1
c2.Nome = "dev" // campo escalar: isolado
c2.Tags[0] = "X" // campo slice: compartilhado
fmt.Println(c1) // {prod [X b]}
fmt.Println(c2) // {dev [X b]}
// 4. Cópia profunda é manual
c3 := c1
c3.Tags = slices.Clone(c1.Tags)
c3.Tags[0] = "Z"
fmt.Println(c1.Tags, c3.Tags) // [X b] [Z b] <- agora sim
// 5. Map nunca é copiado por atribuição
m1 := map[string]int{"a": 1}
m2 := m1
m2["a"] = 99
fmt.Println(m1["a"]) // 99
m3 := maps.Clone(m1)
m3["a"] = 1
fmt.Println(m1["a"], m3["a"]) // 99 1
// 6. append: mesmo array ou array novo, sem aviso
base := []int{1, 2, 3, 4, 5}
vista := base[:2] // len 2, cap 5
vista = append(vista, 99) // cap sobra: escreve em base[2]
fmt.Println(base) // [1 2 99 4 5]
base2 := []int{1, 2, 3}
vista2 := base2[:2] // len 2, cap 3
vista2 = append(vista2, 8, 9) // não cabe: realoca
fmt.Println(base2, vista2) // [1 2 3] [1 2 8 9]
}
== segue exatamente a mesma lógica
Comparação em Go opera sobre os mesmos bytes que a atribuição copia:
*T == *Tcompara endereços, não o que está apontado- struct compara campo a campo, e só compila se todos os campos forem comparáveis
- slice não é comparável:
a == bem[]int→ invalid operation: a == b (slice can only be compared to nil) - struct contendo slice também não, e a mensagem é outra → invalid operation: a == b (struct containing []int cannot be compared)
- para comparação profunda:
slices.Equal,maps.Equal(Go 1.21+) oureflect.DeepEqual time.Timecom==compara wall clock + monotonic + location — use.Equal
Conclusão em três regras
a = bcopiaunsafe.Sizeof(b)bytes. Sempre. Se o resultado te surpreende, olhe o que está dentro desses bytes.- Ponteiro dentro do valor = alias. Slice, map, chan, func e interface têm.
Isolamento exige clone explícito (
slices.Clone,maps.Clone, ou campo a campo). - Cópia profunda não existe em Go. Isso não é omissão: é a linguagem se recusando a esconder uma alocação de você.
Parte de Semana 01 - Diagnóstico e o que Acontece por Baixo · 00 - MOC Fundamentos de Programação