La función copy en Go es una primitiva integrada (built-in) diseñada para transferir elementos entre dos regiones de memoria direccionadas por descriptores de slices o strings, devolviendo un entero que representa la cantidad de elementos efectivamente desplazados. A diferencia de una asignación directa de variables de tipo slice, que únicamente replica la estructura del slice header (puntero, longitud y capacidad), copy ejecuta una duplicación física de los datos sobre el underlying array del destino.
Este comportamiento existe para garantizar la integridad y el aislamiento de los datos en un lenguaje que favorece el paso de referencias ligeras. Resuelve el problema del aliasing accidental, donde múltiples slices apuntan a la misma memoria física y las mutaciones en uno afectan colateralmente a los demás. Mientras que lenguajes como C requieren el uso de memcpy o memmove con gestión manual de punteros y tamaños en bytes, Go proporciona una interfaz con seguridad de tipos que respeta los límites de los descriptores involucrados sin riesgo de desbordamiento de búfer.
Mecánica de transferencia y restricciones de tamaño
Técnicamente, el número de elementos copiados se define mediante la expresión matemática $n = \min(\text{len}(dst), \text{len}(src))$. Esta restricción implica que la función nunca incrementará la capacidad ni la longitud del slice de destino; si el receptor es más pequeño que el emisor, la transferencia se truncará silenciosamente al alcanzar el límite de len(dst). Por el contrario, si el destino es mayor, solo se sobrescribirán los primeros elementos correspondientes a la longitud del origen, preservando intacto el resto del array subyacente.
El mecanismo interno es altamente eficiente debido a que el compilador suele optimizar estas llamadas utilizando instrucciones de movimiento de memoria en bloque. Una característica fundamental es su capacidad para manejar regiones de memoria solapadas (overlapping). Si el slice de destino y el de origen apuntan a diferentes fragmentos del mismo array subyacente, el runtime garantiza que la copia sea correcta, comportándose de manera análoga a memmove en C, donde los datos se leen completamente antes de ser escritos para evitar la corrupción por sobrescritura prematura.
package main
import "fmt"
func main() {
src := []int{1, 2, 3, 4, 5}
dst := make([]int, 3)
// Caso 1: Destino más pequeño que el origen
n1 := copy(dst, src)
fmt.Printf("Copiados: %d, Dst: %v\n", n1, dst) // → Copiados: 3, Dst: [1 2 3]
// Caso 2: Origen más pequeño que el destino
bigDst := []int{0, 0, 0, 0, 0, 0, 0}
n2 := copy(bigDst, src)
fmt.Printf("Copiados: %d, BigDst: %v\n", n2, bigDst) // → Copiados: 5, BigDst: [1 2 3 4 5 0 0]
// Caso 3: Copia de string a []byte (optimización de casting)
str := "Gopher"
byteSlice := make([]byte, 10)
n3 := copy(byteSlice, str) // No requiere []byte(str)
fmt.Printf("Bytes: %d, Content: %s\n", n3, byteSlice[:n3]) // → Bytes: 6, Content: Gopher
}
GoLa sintaxis especial que permite usar un string como origen en una copia hacia un []byte es una optimización crítica del compilador. Evita la asignación de memoria adicional en el heap que ocurriría si se realizara una conversión explícita de tipo antes de la copia, permitiendo tratar el almacenamiento inmutable de la cadena como un origen de datos directo.
Garantía de seguridad en la copia de regiones solapadas
Un comportamiento no obvio del runtime ocurre durante el self-slicing o cuando se manipulan búferes circulares. Debido a que el sistema de tipos de Go conoce las direcciones base y las longitudes a través de los slice headers, el algoritmo de copia detecta si el puntero de destino cae dentro del rango direccionable del origen. En tal situación, el runtime ajusta la dirección de la copia (hacia adelante o hacia atrás) para asegurar que ningún elemento sea sobrescrito antes de ser procesado como fuente.
Este escenario es común al implementar algoritmos de eliminación de elementos in-place. Al desplazar elementos hacia la izquierda para cubrir un hueco dejado por una eliminación, el origen y el destino comparten la vasta mayoría de su backing array. La función copy gestiona esta superposición sin intervención del desarrollador, manteniendo la coherencia de los datos remanentes sin necesidad de estructuras temporales intermedias que aumentarían la presión sobre el Garbage Collector.
- Módulo: Colecciones y Memoria
- Artículo número: #37