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 tail com base[1:].
  • Fazer append(tail, 100) e devolver os dois slices.
  • Mostrar na saída que base foi alterado pelo append em tail.
  • 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

  1. []int{1, 2, 3} cria um array subjacente com 3 inteiros; base tem len=3 e cap=3.
  2. base[1:] cria tail apontando para o índice 1 do mesmo array: len=2, cap=2 (índices 1 e 2 do array).
  3. append(tail, 100) precisa de um slot livre. Como cap(tail)=2 e len(tail)=2, a capacidade está esgotada... mas atenção: o array subjacente tem 3 posições (índices 0, 1, 2) e tail começa no índice 1, então cap(tail) é 2 e está cheio. Aqui, append realoca um novo array, então base nã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

  1. make([]int, 3, 4) cria um array subjacente de 4 posições; base vê as 3 primeiras (len=3, cap=4).
  2. copy(b, []int{1, 2, 3}) preenche base com 1, 2, 3.
  3. base[1:] cria tail com len=2 e cap=3 (índices 1, 2, 3 do array).
  4. append(tail, 100) cabe na capacidade: escreve 100 na posição 3 do array, que é a quarta posição de base.
  5. base agora tem len=3, mas seu array subjacente guarda [1 2 3 100]. Ao imprimir base, 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)

  1. make([]int, 3, 4) cria um array de 4 posições; base tem len=3, cap=4.
  2. copy(b, []int{1, 2, 3}) preenche os 3 elementos visíveis.
  3. base[1:] gera tail com len=2, cap=3 (do índice 1 até o fim do array).
  4. append(tail, 100) cabe na capacidade e escreve 100 na posição 3 do array compartilhado.
  5. b[:cap(b)] re-sliceia base para mostrar todas as 4 posições, revelando o 100 escrito por tail.
  6. A saída confirma o aliasing: base e tail compartilham 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 base com cap == len: o append realoca e base não muda. Use make com capacidade extra para demonstrar o efeito.
  • Esquecer que base tem len=3: imprimir base direto não mostra o 100; é preciso re-slicear com base[:cap(base)].
  • Achar que append sempre 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]