Declarar una función
Una declaración de función es func, un nombre, los parámetros, el tipo del resultado y un cuerpo. El tipo va detrás del nombre del parámetro, no delante.
add recibe dos valores int y devuelve un int. greet no devuelve nada, así que su firma no tiene tipo de resultado y su cuerpo no necesita return.
Una función que declara un resultado tiene que terminar en una sentencia de terminación. Si alguna ruta puede llegar a la llave de cierre sin retornar, el compilador se detiene con missing return.
Parámetros que comparten tipo
Los parámetros consecutivos del mismo tipo pueden compartir un único nombre de tipo. func add(a, b int) int significa exactamente lo mismo que func add(a int, b int) int. También se pueden mezclar:
func scale(x, y float64, factor int) (float64, float64)
Aquí x e y son float64 y factor es un int.
Valores de retorno
Una función puede devolver cero, uno o varios valores. Los resultados múltiples van entre paréntesis, y quien llama los recibe con una asignación múltiple:
Esto imprime 3 2. El par (value, error) es el uso más común de esta característica en Go, y tiene su propia página sobre valores de retorno múltiples.
Los argumentos se copian
Go pasa todos los argumentos por valor. La función trabaja sobre una copia, así que asignar a un parámetro nunca cambia la variable de quien llama.
La salida es:
{1 2}
{100 2}
[99 2 3]
La tercera línea sorprende. Un valor slice es una cabecera pequeña (puntero, longitud, capacidad). La copia apunta al mismo array subyacente, así que escribir s[0] escribe el elemento de quien llama. Pero append dentro de la función puede reservar un array nuevo, y quien llama nunca ve esa cabecera nueva. Si una función hace crecer un slice, devuelve el slice nuevo. La misma lógica vale para los maps: la función puede añadir claves que quien llama verá, porque el valor map apunta a datos compartidos.
Usa un parámetro puntero cuando la función tenga que modificar la variable de quien llama, o cuando el valor sea un struct grande que no quieres copiar en cada llamada.
Las funciones son valores
Una función tiene un tipo, que se escribe sin el nombre: func(int, int) int. Puedes guardar una función en una variable, meterla en un map o un slice y pasarla a otra función.
square es una función anónima asignada a una variable. Las funciones anónimas también pueden leer y cambiar variables del ámbito que las rodea, lo que las convierte en closures.
Un tipo función con nombre hace más legibles las firmas cuando la misma forma aparece en muchos sitios:
type Transform func(int) int
func apply(xs []int, f Transform) []int
El valor cero de un tipo función es nil. Llamar a una función nil provoca un panic con invalid memory address or nil pointer dereference, así que comprueba los callbacks opcionales antes de llamarlos. Los valores función solo se pueden comparar con nil, nunca entre sí.
Recursión
Una función puede llamarse a sí misma. Go no tiene optimización de llamadas de cola, pero las pilas de las goroutines crecen bajo demanda, así que las profundidades de recursión normales no dan problemas.
for i := range 6 cuenta de 0 a 5 y necesita Go 1.22 o posterior.
Lo que no tienen las funciones de Go
- Sin sobrecarga. Dos funciones del mismo paquete no pueden compartir nombre, aunque tengan parámetros distintos.
- Sin argumentos por defecto ni con nombre. Todos los parámetros se pasan en cada llamada, en orden. Un struct de configuración te da campos con nombre y opcionales:
NewServer(Config{Port: 8080}). - Sin funciones con nombre anidadas. Dentro del cuerpo de una función solo puedes declarar funciones anónimas, normalmente asignadas a una variable.
Las listas de argumentos de longitud variable se admiten mediante funciones variádicas.
Funciones exportadas y no exportadas
Una función cuyo nombre empieza por mayúscula (Parse) es exportada y se puede llamar desde otros paquetes. Un nombre en minúscula (parse) solo es visible dentro de su propio paquete. No hay palabras clave public ni private; las mayúsculas son toda la regla.
Errores comunes
- Esperar que el cambio de un parámetro llegue a quien llama. Asignar a un parámetro solo cambia la copia. Devuelve el valor nuevo o recibe un puntero.
- Hacer append dentro de una función e ignorar el resultado.
func add(s []int) { s = append(s, 1) }no afecta a la longitud del slice de quien llama. Escribefunc add(s []int) []inty usas = add(s). - Resultados sin usar. Go permite ignorar un valor de retorno.
go vetno marca un error descartado, pero linters comoerrcheck(parte degolangci-lint) sí. Usa_ =solo cuando ignorarlo sea una decisión.
Preguntas frecuentes
¿Cómo se define una función en Go?
Usa la palabra clave func, un nombre, una lista de parámetros con los tipos detrás de los nombres y el tipo del resultado: func add(a, b int) int { return a + b }. Una función sin resultado omite el tipo, y una función con varios resultados los enumera entre paréntesis: func divmod(a, b int) (int, int).
¿Go pasa los argumentos por valor o por referencia?
Siempre por valor. La función recibe una copia de cada argumento. Para un int o un struct esa copia es independiente de la variable de quien llama. Los slices, maps, channels y punteros son valores pequeños que apuntan a datos compartidos, así que los cambios en los elementos a los que apuntan son visibles para quien llama, pero reasignar el propio parámetro no. Pasa un puntero (*T) cuando la función tenga que cambiar la variable de quien llama.
¿Se puede pasar una función como parámetro en Go?
Sí. Las funciones son valores con un tipo como func(int) int. Declara un parámetro de ese tipo y pasa cualquier función con una firma compatible, con nombre o anónima: func apply(xs []int, f func(int) int).
¿Admite Go sobrecarga de funciones o parámetros por defecto?
No. Cada nombre de función en un paquete tiene que ser único, y todos los parámetros se pasan en cada llamada. Los sustitutos habituales son nombres distintos (NewServer, NewServerWithTLS), un struct de configuración, parámetros variádicos u opciones funcionales.