En Go, los arrays son tipos de valor con tamaño fijo definido en tiempo de compilación. Los slices son vistas dinámicas sobre arrays subyacentes. Entender esta distinción es fundamental porque define cómo se comporta tu código al pasar colecciones a funciones, al usar append y al copiar datos.
Arrays: tamaño fijo y parte del tipo
package main
import "fmt"
func main() {
var a [3]int // [0 0 0]
b := [3]int{1, 2, 3} // literal
c := [...]int{4, 5, 6, 7} // el compilador cuenta: [4]int
fmt.Println(a) // [0 0 0]
fmt.Println(b) // [1 2 3]
fmt.Println(c) // [4 5 6 7]
fmt.Println(len(c)) // 4
}
[3]int y [4]int son tipos distintos e incompatibles. No puedes asignar uno al otro ni pasarlos donde se espera el otro.
Slices: longitud dinámica sobre un array
s := []int{10, 20, 30, 40, 50}
fmt.Println(s[1:3]) // [20 30] desde índice 1 hasta 2 (sin incluir 3)
fmt.Println(s[:2]) // [10 20]
fmt.Println(s[3:]) // [40 50]
fmt.Println(len(s)) // 5 longitud actual
fmt.Println(cap(s)) // 5 capacidad del array subyacente
Un slice tiene tres componentes internos: un puntero al primer elemento del array subyacente, la longitud (len) y la capacidad (cap). Dos slices pueden compartir el mismo array subyacente, lo que tiene implicaciones importantes.
append y crecimiento de capacidad
Cuando añades elementos con append y la capacidad es insuficiente, Go reserva un nuevo array más grande, copia los datos y devuelve un nuevo slice:
s := make([]int, 0, 3) // longitud 0, capacidad 3
for i := 0; i < 6; i++ {
s = append(s, i)
fmt.Printf("len=%d cap=%d %vn", len(s), cap(s), s)
}
// len=1 cap=3 [0]
// len=2 cap=3 [0 1]
// len=3 cap=3 [0 1 2]
// len=4 cap=6 [0 1 2 3] ? nueva reserva, capacidad dobla
// len=5 cap=6 [0 1 2 3 4]
// len=6 cap=6 [0 1 2 3 4 5]
Como append puede devolver un nuevo slice, siempre tienes que asignar el resultado de vuelta a la variable.
Slices comparten memoria: el peligro oculto
original := []int{1, 2, 3, 4, 5}
vista := original[1:3] // [2 3], mismo array subyacente
vista[0] = 99
fmt.Println(original) // [1 99 3 4 5] ? el original cambió
Para evitar este acoplamiento, usa copy:
copia := make([]int, len(vista)) copy(copia, vista) copia[0] = 0 fmt.Println(original) // [1 99 3 4 5] ? sin cambios
copy: copiar entre slices
copy(dst, src) copia el mínimo entre len(dst) y len(src) elementos y devuelve el número copiado:
src := []int{1, 2, 3, 4, 5}
dst := make([]int, 3)
n := copy(dst, src)
fmt.Println(n, dst) // 3 [1 2 3]
Pasar un slice a una función
Un slice se pasa por valor, pero el valor incluye el puntero al array subyacente. La función puede modificar los elementos del array original, pero no puede cambiar la longitud o el puntero que ve el caller:
func doblar(s []int) {
for i := range s {
s[i] *= 2 // modifica el array subyacente compartido
}
}
func main() {
nums := []int{1, 2, 3}
doblar(nums)
fmt.Println(nums) // [2 4 6]
}
