Funciones
Las funciones son una pieza de código independiente que puede aceptar 0 o más parámetros y puede devolver 0 o más valores.
Ya hemos visto funciones anteriormente, la función main
donde ejecutamos
todo nuestro código
func main() {}
Función con parámetros
Como mencionamos anteriormente, las funciones pueden aceptar 0 o más parámetros. Una función que acepta parámetros se puede definir de la siguiente manera:
func add(x int, y int) {
fmt.Println(x + y)
}
func main() {
add(1, 2) // 3
}
En el ejemplo anterior vemos como la función add
acepta los parámetro x
y
y
y ámbos son de tipo int
.
Si queremos retornar el valor de la suma podemos hacerlo de la siguiente manera:
func add(x int, y int) int {
return x + y
}
func main() {
result := add(1, 2)
fmt.Println(result) // 3
}
La ventaja de devolver 1 o más valores es que podemos almacenarlo en variables, hacer comparaciones, etc.
Para devolver multiples valores podemos hacerlo de la siguiente manera:
func add(x int, y int) (int, string) {
result := x + y
message := "El resultado es "
return result, message
}
func main() {
result, message := add(1, 2)
string := fmt.Sprintln(message, result)
fmt.Println(string)
}
En el ejemplo anterior estamos retornando dos valores usando la palabra reservada
return
y separando ambos valores usando una coma. Puedes retornar cuantos valores
desees de tus funciones.
Las funciones no tienen acceso a nada de lo que esté dentro de la función main
o
dentro de otras funciones, las únicas formas de acceder es pasando dichos valores
como argumentos, declarando dichos valores en scope
global, es decir, fuera de nuestras
funciones o que estemos creando un closure
. Por ejemplo:
x := 5
func add(y int) int {
return x + y
}
func main() {
y := 2
fmt.Println(add(y))
}
En el ejemplo anterior nuestra función add
no tiene acceso a y
así que tenemos
que pasarle dicho valor como argumento y tiene acceso a x
gracias a que lo
definimos en el scope
global, es decir, afuera de todas nuestras funciones.
Si nuestra función acepta muchos parametros del mismo tipo, podemos usar la
expresión ...
, esto solo lo podemos hacer en el último parámetro. Por ejemplo:
func add(args ...int) int {
total := 0
for _, value := range args {
total = total + value
}
return total
}
func main() {
values := []int{1,2,3}
result1 := add(values...)
result2 := add(1, 2, 3, 4, 5, 6)
fmt.Println(result1) // 6
fmt.Println(result2) // 21
}
En el ejemplo anterior el parámetro args recibirá 0 o más valores, luego
usamos un ciclo for
para recorrer dichos valores y adicionarlos a la variable
total.
Closure
Es posible crear funciones dentro de otras funciones
func main() {
x := 5
add := func(y int) int {
return x + y
}
result := add(5)
fmt.Println(result) // 10
}
En el ejemplo anterior estamos creando una variable que pertenece a la función
main
y tiene como valor una función. La variable x
también pertenece a la
función main
por lo que tenemos acceso a ella desde dentro de la función
almacenada en la variable add
.
Recursión
Es cuando una función se llama así misma hasta que cumpla cierta condición. Por ejemplo:
func factorial(x uint) uint {
if x == 0 {
return 1
}
return x * factorial(x-1)
}
En el ejemplo anterior la función factorial
se llama así misma hasta que cumpla
la condición de que x == 0
. Si no agregamos una condición la función se llamará
así misma infinitamente, así que debemos tener cuidado con eso.
Defer
Go tiene una expresión especial que nos permite agendar que una función se ejecute luego de que otra haya terminado. Por ejemplo:
func first() {
fmt.Println("First function")
}
func second() {
fmt.Println("Second function")
}
func main() {
defer second()
first()
// First function
// Second function
}
Como podemos observar en el ejemplo anterior, la llamada a la segunda función está
antes que la primera, por lo que debería aparecer primero "Second function", pero
como estamos usando la expersión defer
, la ejecución de la función second
se
ejecuta luego de que la función first
ha terminado.
Usualmente usamos defer
cuando queremos liberar algún recurso que hayamos usado.
Por ejemplo:
f, _ := os.Open("file.txt")
defer f.Close()
// rest of our code
Esto tiene varios beneficios. El primero es que nuestro código es más legible ya
que podemos ver donde leémos y donde cerramos nuestro archivo en el mismo lugar.
Segundo, si tenemos multiples return
dentro de un if
y else
el Close
sucederá antes que estos. Tercero, el defer
se ejecutará incluso si se ejecuta
un panic
Panic & Recover
Panic
se encarga de lanzar un error de ejecución y Recover
se encarga de manejar
ese error. recover
para el panic
y retorna el valor que se pasó como argumento
a panic
. Podemos estar tentados a usarlo de la siguiente manera:
func main() {
panic("Algo malo sucedió")
error := recover()
fmt.Println(error)
}
Pero en este caso el recover
nunca sucederá ya que el panic
para la ejecución
de nuestro programa. Para que esto funcione debemos combinarlo con un defer
.
func main() {
defer func() {
str := recover()
fmt.Println(str)
}()
panic("Algo malo sucedió")
}
En este caso primero ejecutamos la función con defer
, esto hará que el programa
no se cierre hasta que dicha función retorne, por lo que al ejecutar el panic
la función con defer
aún no ha sido ejecutada, por lo tanto es capáz de hacer un
recover
del panic
.
En resumen
Hemos observado que las funciones son una parte fundamental de todos nuestros programas, nos permiten encapsular partes de nuestro código y hacer que este sea más modular y legible.