Que Son Los Slices En Go: Guía Esencial

Imagina que estás armando una lista de tareas en Go, pero en lugar de quedarte atado a un tamaño fijo como con los arrays, puedes estirarla o acortarla sin dramas. Eso resume lo que son los slices en Go: una herramienta flexible que potencia tu código diario.

Esta guía esencial sobre qué son los slices en Go te lleva de cero a experto, con ejemplos reales y trucos prácticos.

¿Qué son exactamente los slices?

Los slices en Go son vistas dinámicas sobre arrays subyacentes. No son contenedores independientes, sino referencias a porciones de memoria.

💡 Si estás considerando migrar a Linux para tu PC o servidor, no te pierdas un análisis equilibrado de sus pros y contras clave que te ayudará a decidir si es ideal para ti.

Piensa en un slice como una ventana deslizante sobre un array. Cambia el tamaño sin copiar todo. ¿Te has topado con límites en otros lenguajes? Go lo soluciona aquí.

Tienen tres componentes clave: un puntero al array, una longitud actual y una capacidad máxima. La longitud es cuántos elementos ves ahora.

La capacidad define hasta dónde puedes crecer sin reallocar. Eficiencia pura, ¿no crees?

Un slice vacío es válido: var s []int. No apunta a nada, pero puedes usarlo con append.

Que Son Los Slices En Go

💡 Si estás migrando a la nube o escalando tu proyecto, descubre los servicios clave de Google Cloud Platform (GCP) que todo dev debe dominar para optimizar costos y rendimiento al instante.

Slices vs arrays: la diferencia clave

Arrays en Go tienen tamaño fijo en compile-time. Declaras [5]int, y listo, cinco enteros para siempre.

Slices, en cambio, son dinámicos. Su longitud varía en runtime. ¿Por qué elegir slices? Porque el 99% del tiempo necesitas flexibilidad.

AspectoArraySlice
TamañoFijo al compilarDinámico
Declaración[n]T[]T
Uso comúnRaro, bajo nivelListas, datos vars
MemoriaContigua fijaReferencia a array

Arrays son raros en código real. Slices dominan por su comodidad. ¿Recuerdas pelear con vectores en C++? Olvídalo.

💡 Si te apasiona la tecnología y buscas razones para sumergirte en el mundo del código, explora los principales beneficios de programar y descubre cómo puede transformar tu carrera y creatividad diaria.

Slices comparten datos con el array original. Modificar uno afecta al otro. Cuidado con efectos secundarios.

Cómo crear un slice paso a paso

La forma más simple: desde un array. Toma un array y “córtalo” con notación array[low:high].

Low es inclusivo, high exclusivo. Ejemplo clásico:

package main

💡 Si estás explorando el mundo de la tecnología portátil, descubre en esta [guía completa sobre definición y características de dispositivos móviles](/todo-sobre-dispositivos-moviles-definicion-y-caracteristicas/) todo lo esencial para entender su evolución y funciones clave.

import "fmt"

func main() {
    cursos := [6]string{"Go", "CSS", "Javascript", "Vue", "Bases de Datos", "DevOps"}
    frontend := cursos[1:4]
    fmt.Println(frontend)  // [CSS Javascript Vue]
}

Aquí, cursos[1:4] crea un slice desde índice 1 hasta 3. Fácil y rápido.

Omite low para empezar en 0: cursos[:3]. Omite high para ir al final: cursos[3:].

¿Quieres todo? cursos[:]. Copia la referencia completa.

💡 Si estás debatiendo si migrar tu negocio a la nube o no, no te pierdas un análisis equilibrado de los pros y contras de la computación en la nube para decidir con total claridad.

Slices literales son aún más directos: []string{"Alvaro", "Alexys", "Beto"}. Go infiere el tipo.

Para control total, usa make: make([]int, len, cap). Len inicializa longitud, cap la capacidad.

numeros := make([]int, 3, 5)  // Longitud 3, capacidad 5, valores en cero
fmt.Println(numeros)  // [0 0 0]
fmt.Println(len(numeros), cap(numeros))  // 3 5

¿Por qué cap extra? Para crecer sin copias costosas. Optimización inteligente.

Operaciones esenciales con slices

Append es el rey: agrega elementos. s = append(s, elem). Si excedes cap, Go reallocará.

s := []int{1, 2}
s = append(s, 3, 4)  // [1 2 3 4]

Múltiples appends en cadena: append(s, elem1, elem2...). Variádico, genial.

¿Copiar slices? Usa copy: copy(dst, src). Copia min(len(dst), len(src)) elementos.

src := []int{1, 2, 3}
dst := make([]int, 2)
copy(dst, src)  // dst es [1 2]

No modifica src. Perfecto para evitar shares no deseados.

Slices de slices: multidimensionales dinámicos. [][]int{{1,2}, {3}}.

Pero ojo, no son rectangulares. Cada sub-slice es independiente.

Longitud, capacidad y crecimiento

len(s) da elementos actuales. cap(s) el tope.

Al appendear, si len+1 > cap, Go duplica cap (aprox) y copia. Amortizado O(1).

Ejemplo:

s := make([]int, 0, 10)
for i := 0; i < 20; i++ {
    s = append(s, i)
}
fmt.Println(cap(s))  // Algo como 20 o 32, según impl

¿Por qué importa? Evita reallocs frecuentes en loops. Prealloca con make.

Pregunta común: ¿cómo recortar capacidad? Crea nuevo slice: s = s[:len(s):cap_nueva].

low:high:cap_max limita cap explícitamente. Truco avanzado.

Slices en strings y bytes

Strings son inmutables, pero s[i:j] da sub-string. No slice de chars, sino bytes.

Slices de bytes son flexibles: []byte("hello")[1:4] da [101 108 108].

Útil para parsing. Eficiencia en texto.

Modificar slice de bytes cambia el string original si lo conviertes back. Cuidado.

Ejemplos prácticos en el mundo real

Supongamos un servidor web. Lista de requests: slice de structs.

type Request struct {
    ID   int
    Path string
}

requests := make([]Request, 0, 100)  // Preallocar
// En handler:
requests = append(requests, newReq)

¿Filtrar? Loop con append a nuevo slice. O usa generics en Go 1.18+ para map/filter.

Otro: lectura de archivos. ioutil.ReadFile da []byte. Slicealo por líneas.

Humor: sin slices, estarías reinventando la rueda como en los 90s. Go te salva.

¿Qué pasa con nil slices? len==0, cap==0. Append funciona: var s []int; s=append(s,1).

Iterando y modificando slices

Range es tu amigo: for i, v := range s {}. Índice y valor por copia.

Para modificar: for i := range s { s[i]++ }. O punteros: for i, v := range s { *(&s[i])++ }. Feo, mejor índices.

for i, v := range s[:] copia slice para evitar shares raros.

Errores comunes y cómo evitarlos

Cap vs len: no confundas. Append usa len, pero respeta cap.

Share de datos: s1 := s[0:5]; s1[0]=99 cambia s. ¿Quieres copia? Usa copy.

Out-of-bounds: Go paniquea. Siempre chequea i < len(s).

Slices grandes: reallocs consumen memoria. Profilea con pprof.

Pregunta: ¿por qué no vectores como C++? Go prioriza simplicidad. Slices bastan.

Slices en funciones: pasar por referencia

Slices se pasan por valor, pero el puntero subyacente es referencia. Eficiente.

Func func add(s []int) modifica el slice backing.

Para readonly: s []int o range.

Variádicos: func sum(nums ...int). Llama como sum(1,2,3) o sum(s...).

Genial para helpers.

Mejores prácticas para slices pros

  • Prealloca con make: make([]T, 0, expected).
  • Usa append en loops, no índices manuales.
  • Para deletes: s = append(s[:i], s[i+1:]...). Eficiente O(n).
  • En concurrencia: slices no son thread-safe. Usa channels o sync.Mutex.
  • Benchmarks: mide append vs prealloc. Diferencia enorme en grandes datos.

Tabla de costos aproximados:

OperaciónCosto promedio
AppendO(1) amortizado
CopyO(n)
SliceO(1)
DeleteO(n)

Casos avanzados: slices de structs y más

Slices de structs: comunes en APIs. []User{}.

Zero values: structs en cero al make.

Slices como keys en maps: no, porque no comparable. Usa strings o índices.

iota con const para slices: útil en gens.

Go 1.21+: slices en for range más eficientes.

Por qué amar los slices en Go

Han pasado años desde que descubrí qué son los slices en Go, y siguen siendo mi feature favorita. Simplifican código sin magia negra.

¿Dudas? Prueba el playground de Go. Experimenta.

Esta guía esencial cubre lo básico y avanzado. Siguiente paso: integra en tu proyecto.

¿Listo para slices dinámicos en acción? Corre los ejemplos y ve la magia. Go te espera.