Una función variádica en Go es aquella que acepta un número cero o más argumentos de un tipo específico mediante el uso del elipsis ... en la declaración de su último parámetro. Técnicamente, esta construcción permite que el llamador pase una lista de argumentos de longitud arbitraria, los cuales son capturados por el receptor como una entidad única de tipo coleccionable, proporcionando una interfaz flexible para funciones de agregación, formateo o procesamiento de colecciones.
Este comportamiento existe en Go para facilitar la creación de APIs idiomáticas, como se observa en el paquete estándar con fmt.Printf o append. A diferencia de lenguajes que requieren sobrecarga de métodos (method overloading) para manejar distintos números de argumentos, Go simplifica el diseño del lenguaje manteniendo una única firma de función que gestiona la variabilidad. Esto resuelve la verbosidad de construir manualmente estructuras de datos temporales solo para pasar un conjunto de elementos a una subrutina.
Implementación y conversión interna a slice
Desde la perspectiva del sistema de tipos, el parámetro variádico ...T es tratado dentro del cuerpo de la función exactamente como un slice del tipo []T. El compilador realiza una transformación automática: cuando la función es invocada con argumentos individuales, el runtime asigna un nuevo slice, copia los valores proporcionados en el underlying array de este y pasa el descriptor del slice resultante a la función.
Es fundamental comprender que la propiedad variádica es una característica de la firma de la función en el punto de llamada, pero no altera la naturaleza del dato dentro del scope de la función. El identificador variádico siempre debe ser el último parámetro declarado. Si se intenta declarar parámetros adicionales después del elipsis, el compilador generará un error de sintaxis, ya que no habría una forma inequívoca de determinar dónde termina la lista de argumentos variables y dónde comienzan los parámetros fijos.
package main
import "fmt"
// Sum recibe cero o más enteros. Internamente 'nums' es []int.
func Sum(nums ...int) int {
total := 0
for _, num := range nums {
total += num
}
return total
}
func main() {
// Llamada con argumentos individuales
fmt.Println(Sum(1, 2, 3)) // → 6
// Llamada con cero argumentos: nums será un slice nil o vacío
fmt.Println(Sum()) // → 0
// Uso del operador de expansión para pasar un slice existente
data := []int{10, 20, 30}
fmt.Println(Sum(data...)) // → 60
}
GoEl uso del operador de expansión ... al invocar una función variádica es la operación inversa a la declaración en la firma. Al sufijar un slice con elipsis, se le indica al compilador que no debe envolver el slice en uno nuevo, sino pasar el descriptor del slice existente directamente. Esto es crucial para la eficiencia, ya que evita una asignación de memoria adicional y la copia de los elementos en el stack.
Mutación del backing array en llamadas variádicas
Un comportamiento que suele pasar desapercibido es la relación de pertenencia entre el slice original y el parámetro variádico cuando se utiliza la expansión. Cuando pasas un slice s... a una función que acepta ...T, no se crea una copia profunda de los datos. El parámetro dentro de la función comparte el mismo backing array que el slice original del llamador.
Si la función variádica modifica los elementos del slice recibido, estos cambios se reflejarán en el slice del llamador, rompiendo potencialmente la transparencia referencial. Este es un edge case crítico en funciones que realizan operaciones de ordenamiento o limpieza de datos in-place. Para evitar efectos secundarios no deseados, si la función necesita alterar los datos, debe realizar una copia explícita internamente antes de operar sobre el slice capturado.
- Módulo: Funciones
- Artículo número: #70