Copia de Cabecera de Slice y Sorpresa de Append
Dos funciones comparten lo que parecen ser slices independientes hasta que un append sobrescribe los elementos del otro.
Busca en todas las páginas de la documentación
Dos funciones comparten lo que parecen ser slices independientes hasta que un append sobrescribe los elementos del otro.
Esta página muestra cómo la copia de cabecera de slice crea aliasing, cómo append reutiliza la capacidad y los patrones de copia que previenen la corrupción de datos en producción.
Un slice de Go es una pequeña cabecera: puntero, longitud y capacidad.
La asignación y el paso por valor copian la cabecera, no el array subyacente.
Cuando dos cabeceras apuntan al mismo array, append en un slice puede mutar elementos visibles a través del otro si existe capacidad libre.
Estos errores son insidiosos porque los programas rara vez entran en pánico; devuelven datos incorrectos bajo tamaños y patrones de carga específicos.
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
// Limita la capacidad al exponer una vista interior
sub := s[low:high:high] // cap == len, append reasigna
// Copia defensiva antes de pasar al llamador
out := append([]T(nil), s...)
// Copia solo el prefijo
prefix := append([]T(nil), s[:n]...)Cuándo usar esto:
[]byte reutilizados entre solicitudesappend después de s[:n] en almacenamiento compartido[]byte en capas HTTP/gRPCpackage main
import "fmt"
func main() {
base := []int{1, 2, 3, 4, 5}
window := base[1:3] // len 2, cap 4 (comparte el array subyacente)
fmt.Println("antes", base, window, "cap", cap(window))
window = append(window, 99) // escribe en el array subyacente de base
fmt.Println("después ", base, window)
safe := append([]int(nil), base[1:3]...)
safe = append(safe, 99)
fmt.Println("seguro ", base, safe)
}Lo que esto demuestra:
window comparte el array de base; append con capacidad libre muta basecap(window) mayor que len(window) es la señal de peligroappend aísla las mutacioneslen, cap y el slice subyacente completo detecta el defecto en las pruebaspuntero | len | caps[low:high] establece len=high-low, cap=cap(s)-low - a menudo mayor que lenappend reutiliza el array subyacente cuando len < cap; de lo contrario, asigna un nuevo arraycap compartida significa que append puede sobrescribir índices más allá de la len del sub-slice pero dentro de la cap del padre| Objetivo | Patrón |
|---|---|
| Exponer vista de solo lectura | Devolver copia append([]T(nil), s...) |
| Vista interior sin crecimiento de append hacia el padre | Slice de tres índices s[i:j:j] |
| Truncar pero mantener cap | s = s[:n:n] antes de pasar |
| Restablecer buffer reutilizable | s = s[:0] solo cuando es el único propietario |
// Analizador que mantiene un buffer []byte
func parseFrame(buf []byte) (payload []byte, rest []byte) {
n := int(buf[0])
payload = append([]byte(nil), buf[1:1+n]...) // copia el payload
rest = buf[1+n:]
return payload, rest
}append siempre asigna - Reutiliza la capacidad cuando es posible. Solución: verifica cap o copia primero.s[:n] desde un buffer interno - Los llamadores hacen append en tu pool. Solución: copia o limita la capacidad con tres índices.bytes.Buffer.Bytes() - Los bytes permanecen válidos hasta la próxima mutación del buffer. Solución: copia si almacenas a largo plazo.s[low:high:high].append concurrente en slice compartido - Carrera de datos incluso sin pánico. Solución: mutex, copia en escritura o buffer por goroutine.s = nil antes de descifrar si necesitas una nueva asignación.| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
| Slice de tres índices | Vista de cero copias, sin append por el consumidor | El consumidor necesita espacio para append |
append([]T(nil), s...) | Entregar la propiedad al llamador | La ruta crítica necesita cero copias y el contrato prohíbe append |
slices.Clip (Go 1.21+) | Recortar cap a len en una llamada | Debe soportar Go más antiguo sin polyfill |
Pool de []byte separado por goroutine | Analizadores de alto rendimiento | Rutas de código simples donde la copia es barata |
Los slices son vistas ligeras.
Copiar el array en cada asignación destruiría el rendimiento.
El compartir se hace explícito a través de las reglas de capacidad.
Busca s[low:high] almacenado o devuelto, luego append en el slice derivado.
Verifica si el slice padre todavía está activo.
El range sobre un slice copia el valor de cada elemento.
Mutar campos de struct a través del valor del range no actualiza el slice; la asignación por índice sí lo hace.
Las strings son inmutables.
Las sub-strings comparten bytes subyacentes pero no se les puede hacer append.
slices.Clone(s) asigna un nuevo slice con los elementos copiados.
Úsalo cuando quieras una utilidad de la librería estándar en lugar de append([]T(nil), s...).
Sí - los slices externos e internos tienen cabeceras.
Copia los slices internos al pasar filas a trabajo asíncrono.
Muta el slice derivado, verifica que el padre no cambie.
Prueba de tabla con longitudes donde cap > len.
No - solo el compartir capacidad afecta a los elementos dentro de cap.
La len del padre no cambia a menos que asignes de vuelta a la variable padre.
Reader gestiona la posición pero el slice subyacente aún puede tener aliasing.
Copia si los consumidores retienen vistas []byte.
log.Printf("base=%p len=%d cap=%d %#v", &base[0], len(base), cap(base), base) antes y después de append en el slice derivado.
No - las cabeceras de slice se comportan igual para []T con cualquier T.
Analizadores críticos y stacks de red.
Documenta que los consumidores no deben hacer append; impón con capacidad de tres índices o tipo opaco.
Versiones de Stack: Esta página fue escrita para Go 1.26.x (predeterminado GC Green Tea,
go fix modernizers- verifica el parche en la compilación), chi (última - verifica en la compilación), gin (última - verifica en la compilación), echo (última - verifica en la compilación), google.golang.org/grpc (última - verifica en la compilación), sigs.k8s.io/controller-runtime (última - verifica en la compilación), kubebuilder (última - verifica en la compilación), tinygo (última - verifica objetivos de placa en la compilación), wazero (última - verifica en la compilación) y golangci-lint (última - verifica el conjunto de linters en la compilación).
Revisado por Chris St. John·Última actualización: 16 jul 2026