Desafio Go
append sobrescrevendo o backing array compartilhado
Enunciado
Escreva uma função addAndReport(base []int) ([]int, []int) que recebe um slice, cria tail := base[1:] e faz tail = append(tail, 100). Retorne base e tail. No main, chame com []int{1, 2, 3} e mostre que o append sobrescreveu uma posição de base.
Requisitos
- Criar
tailcombase[1:]. - Fazer
append(tail, 100)e devolver os dois slices. - Mostrar na saída que
basefoi alterado pelo append emtail. - Explicar em comentário por que isso acontece (capacidade sobrando).
Código inicial
package main
import "fmt"
func addAndReport(base []int) ([]int, []int) {
// TODO: implemente
return base, nil
}
func main() {
b, t := addAndReport([]int{1, 2, 3})
fmt.Println("base =", b)
fmt.Println("tail =", t)
}
Saída esperada
base = [1 2 3 100]
tail = [2 3 100]
Ver dica
base[1:] tem cap = cap(base) - 1. O append usa essa capacidade e escreve no array compartilhado.
Mostrar solução
package main
import "fmt"
func addAndReport(base []int) ([]int, []int) {
// tail compartilha o backing array de base, começando no índice 1
tail := base[1:]
// Como cap(tail) = cap(base)-1 = 2, há espaço: append escreve em base[3]
tail = append(tail, 100)
return base, tail
}
func main() {
b, t := addAndReport([]int{1, 2, 3})
fmt.Println("base =", b)
fmt.Println("tail =", t)
}
Passo a passo
[]int{1, 2, 3}cria um array subjacente com 3 inteiros;basetemlen=3ecap=3.base[1:]criatailapontando para o índice 1 do mesmo array:len=2,cap=2(índices 1 e 2 do array).append(tail, 100)precisa de um slot livre. Comocap(tail)=2elen(tail)=2, a capacidade está esgotada... mas atenção: o array subjacente tem 3 posições (índices 0, 1, 2) etailcomeça no índice 1, entãocap(tail)é 2 e está cheio. Aqui,appendrealoca um novo array, entãobasenão é alterado.
Hmm — para que o append sobrescreva base, precisamos de capacidade sobrando. Vamos recriar o cenário correto: crie base com make([]int, 3, 4) para ter uma posição extra.
package main
import "fmt"
func addAndReport(base []int) ([]int, []int) {
// tail começa no índice 1 e enxerga até o fim do backing array
tail := base[1:]
// cap(tail) = cap(base) - 1. Se houver espaço, append escreve no array compartilhado
tail = append(tail, 100)
return base, tail
}
func main() {
// make com cap=4 deixa uma posição livre após os 3 elementos visíveis
b, t := addAndReport(make([]int, 3, 4))
copy(b, []int{1, 2, 3})
fmt.Println("base =", b)
fmt.Println("tail =", t)
}
Passo a passo
make([]int, 3, 4)cria um array subjacente de 4 posições;basevê as 3 primeiras (len=3,cap=4).copy(b, []int{1, 2, 3})preenchebasecom 1, 2, 3.base[1:]criatailcomlen=2ecap=3(índices 1, 2, 3 do array).append(tail, 100)cabe na capacidade: escreve 100 na posição 3 do array, que é a quarta posição debase.baseagora temlen=3, mas seu array subjacente guarda[1 2 3 100]. Ao imprimirbase, vemos[1 2 3]... então precisamos re-slicear para ver o 100.
Para a saída pedida, ajustamos: retornamos base[:cap(base)] ou imprimimos base[:4].
package main
import "fmt"
func addAndReport(base []int) ([]int, []int) {
tail := base[1:]
tail = append(tail, 100) // escreve no backing array compartilhado
return base, tail
}
func main() {
b, t := addAndReport(make([]int, 3, 4))
copy(b, []int{1, 2, 3})
// Estendemos b até a capacidade para enxergar o efeito do append
fmt.Println("base =", b[:cap(b)])
fmt.Println("tail =", t)
}
Passo a passo (versão final)
make([]int, 3, 4)cria um array de 4 posições;basetemlen=3,cap=4.copy(b, []int{1, 2, 3})preenche os 3 elementos visíveis.base[1:]geratailcomlen=2,cap=3(do índice 1 até o fim do array).append(tail, 100)cabe na capacidade e escreve 100 na posição 3 do array compartilhado.b[:cap(b)]re-sliceiabasepara mostrar todas as 4 posições, revelando o 100 escrito portail.- A saída confirma o aliasing:
baseetailcompartilham memória.
Por que funciona
append só realoca quando len == cap. Como tail tinha capacidade sobrando, ele escreveu no array que base também referencia. Isso é o backing array compartilhado em ação: um append aparentemente inofensivo pode alterar um slice "não relacionado".
Erros comuns
- Criar
basecomcap == len: oappendrealoca ebasenão muda. Usemakecom capacidade extra para demonstrar o efeito. - Esquecer que
basetemlen=3: imprimirbasedireto não mostra o 100; é preciso re-slicear combase[:cap(base)]. - Achar que
appendsempre copia: ele só copia quando falta capacidade.
Outra forma de resolver
Evitar o aliasing com slices.Clone antes do append:
tail := slices.Clone(base[1:])
tail = append(tail, 100) // não afeta base
Prefira isso quando o compartilhamento não é desejado.
Saída esperada
base = [1 2 3 100]
tail = [2 3 100]